﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Net.Http;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TrinityAPI.Base;
using TrinityAPI.Base.Models;
using TrinityAPI.Base.Tools;

namespace TrinityAPI.Tools
{
    public static class APIHelper
    {
        /// <summary>
        /// 将HTTP请求的Uri转成JSON对象
        /// </summary>
        /// <param name="Request">HTTP请求实体</param>
        /// <returns>JSON</returns>
        public static JToken GetRequestUri(HttpRequestMessage Request)
        {
            if (Request == null)
                return null;
            JObject uri = null;
            if (Request.RequestUri != null)
            {
                uri = new JObject();
                uri.Add("AbsolutePath", Request.RequestUri.AbsolutePath);
                uri.Add("AbsoluteUri", Request.RequestUri.AbsoluteUri);
                uri.Add("Authority", Request.RequestUri.Authority);
                uri.Add("DnsSafeHost", Request.RequestUri.DnsSafeHost);
                uri.Add("Fragment", Request.RequestUri.Fragment);
                uri.Add("Host", Request.RequestUri.Host);
                uri.Add("HostNameType", Request.RequestUri.HostNameType.ToString());
                uri.Add("IsAbsoluteUri", Request.RequestUri.IsAbsoluteUri);
                uri.Add("IsDefaultPort", Request.RequestUri.IsDefaultPort);
                uri.Add("IsFile", Request.RequestUri.IsFile);
                uri.Add("IsLoopback", Request.RequestUri.IsLoopback);
                uri.Add("IsUnc", Request.RequestUri.IsUnc);
                uri.Add("LocalPath", Request.RequestUri.LocalPath);
                uri.Add("OriginalString", Request.RequestUri.OriginalString);
                uri.Add("PathAndQuery", Request.RequestUri.PathAndQuery);
                uri.Add("Port", Request.RequestUri.Port);
                uri.Add("Query", Request.RequestUri.Query);
                uri.Add("Scheme", Request.RequestUri.Scheme);
                uri.Add("Segments", new JArray(Request.RequestUri.Segments));
                uri.Add("UserEscaped", Request.RequestUri.UserEscaped);
                uri.Add("UserInfo", Request.RequestUri.UserInfo);
            }
            return uri;
        }

        /// <summary>
        /// 将HTTP请求的Header转成JSON对象
        /// </summary>
        /// <param name="Request">HTTP请求实体</param>
        /// <returns>JSON</returns>
        public static JToken GetRequestHeaders(HttpRequestMessage Request)
        {
            if (Request == null)
                return null;
            JObject header = null;
            string contentType = Request.Content.Headers?.ContentType?.ToString();
            string charset = Utils.GetRequestCharset(contentType);
            if (Request.Headers != null && Request.Headers.Count() > 0)
            {
                header = new JObject();
                var cookie = new JObject();
                foreach (var item in Request.Headers.GetCookies())
                {
                    foreach (var c in item.Cookies)
                    {
                        var values = new JObject();
                        foreach (string key in c.Values.AllKeys)
                            values.Add(key, c.Values[key]);
                        cookie[c.Name] = new JObject() {
                            //{ "domain", item.Domain },
                            //{ "path", item.Path },
                            //{ "expires", item.Expires },
                            //{ "maxAge", item.MaxAge },
                            //{ "secure", item.Secure },
                            //{ "httpOnly", item.HttpOnly },
                            { "value", c.Value },
                            { "values", values }
                        };
                    }
                }
                foreach (var item in Request.Headers)
                {
                    if (item.Key.Equals("cookie", StringComparison.OrdinalIgnoreCase))
                    {
                        header[item.Key] = cookie;
                        continue;
                    }
                    IEnumerable<string> values = null;
                    if (Request.Headers.TryGetValues(item.Key, out values))
                    {
                        string value = string.Join(",", values);
                        header[item.Key] = HttpUtility.UrlDecode(value, Encoding.GetEncoding(charset));
                    }
                    else
                    {
                        header[item.Key] = string.Empty;
                    }
                }
            }
            if (!string.IsNullOrWhiteSpace(contentType))
            {
                if (header == null)
                    header = new JObject();
                header.SetJsonObjectValue("Content-Type", contentType);
            }
            return header;
        }

        /// <summary>
        /// 将HTTP请求的Url参数转成JSON对象
        /// </summary>
        /// <param name="Request">HTTP请求实体</param>
        /// <param name="id">资源ID</param>
        /// <returns>JSON</returns>
        public static JToken GetRequestQuerys(HttpRequestMessage Request, string id = null)
        {
            if (Request == null)
                return null;
            JObject query = null;
            string charset = Utils.GetRequestCharset(Request.Content.Headers?.ContentType?.ToString());
            if (!string.IsNullOrWhiteSpace(Request.RequestUri.Query))
            {
                query = new JObject();
                foreach (var item in Request.RequestUri.Query.TrimStart('?').Split('&'))
                {
                    string[] kv = item.Split('=');
                    if (kv.Length == 2)
                    {
                        string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset));
                        string value = HttpUtility.UrlDecode(kv[1], Encoding.GetEncoding(charset));
                        query[key] = value;
                    }
                    else if (kv.Length == 1)
                    {
                        string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset));
                        query[key] = string.Empty;
                    }
                }
            }
            if (!string.IsNullOrWhiteSpace(id))
            {
                if (query == null)
                    query = new JObject();
                query["id"] = id;
            }
            return query;
        }

        /// <summary>
        /// 将返回参数模板解析为具体实体
        /// </summary>
        /// <param name="config">JSON模板</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>返回参数实体</returns>
        public static JToken ParseResponse(JToken config, JObject dataset)
        {
            if (config == null)
                return null;

            if (config.Type == JTokenType.String)
            {
                string msg = config.ToString();
                if (string.IsNullOrWhiteSpace(msg))
                    return msg;
                var indexAdjustment = 0;
                var parameterNames = new Dictionary<string, string>();
                var matches = Regex.Matches(msg, Utils.ParamRegexPattern);
                foreach (Match match in matches)
                {
                    Capture capture = match.Groups[0];
                    if (capture.Value == msg) //只有一个标签变量的情况下，按变量解析返回
                        return Utils.ParseObject(config, dataset);
                    int index = capture.Index + indexAdjustment;
                    int length = capture.Length;
                    string name = capture.Value;
                    string paramname = Utils.GetSha1Hash(name);
                    if (parameterNames.ContainsKey(paramname))
                    {
                        msg = string.Format("{0}{1}{2}", msg.Substring(0, index), parameterNames[paramname], msg.Substring(index + length));
                        indexAdjustment += parameterNames[paramname].Length - name.Length;
                        continue;
                    }

                    List<KeyValuePair<string, string>> functions = null;
                    string path = match.Groups[1].Value;
                    string[] paths = path.Split('.');
                    string suffix = paths[paths.Length - 1];
                    if (!string.IsNullOrWhiteSpace(match.Groups[2].Value))
                    {
                        functions = new List<KeyValuePair<string, string>>();
                        functions.Add(new KeyValuePair<string, string>(suffix, match.Groups[3].Value));
                        for (int i = 0; i < match.Groups[4].Captures.Count; i++)
                            functions.Add(new KeyValuePair<string, string>(match.Groups[4].Captures[i].Value, match.Groups[5].Captures[i].Value));
                        path = path.Substring(0, path.Length - suffix.Length).TrimEnd('.');
                    }

                    string valueString = string.Empty;
                    var value = Utils.GetParameter(path, dataset, functions).Evaluate();
                    if (value != null)
                    {
                        if (value is JValue)
                            valueString = value.ToString();
                        else
                            valueString = value.ToString(Formatting.None);
                    }

                    msg = string.Format("{0}{1}{2}", msg.Substring(0, index), valueString, msg.Substring(index + length));
                    parameterNames.Add(paramname, valueString);
                    indexAdjustment += valueString.Length - name.Length;
                }

                return msg;
            }

            return Utils.ParseObject(config, dataset);
        }

        /// <summary>
        /// 接口初始化（加载配置）
        /// </summary>
        /// <param name="service">服务名称</param>
        /// <param name="version">版本号</param>
        /// <param name="apiname">接口名</param>
        /// <param name="filepath">配置文件路径</param>
        /// <param name="basepath">配置文件基路径</param>
        /// <returns>配置信息</returns>
        public static JObject APIInit(string service, string version, string apiname, string filepath = null, string basepath = null)
        {
            if (string.IsNullOrWhiteSpace(apiname) && string.IsNullOrWhiteSpace(filepath))
                return null;

            string configExtension = System.Configuration.ConfigurationManager.AppSettings["configExtension"];
            if (string.IsNullOrWhiteSpace(configExtension))
                configExtension = ".config";
            string configDirectory = System.Configuration.ConfigurationManager.AppSettings["ConfigDirectory"];
            if (string.IsNullOrWhiteSpace(configDirectory))
                configDirectory = Utils.GetMapPath("~/Configs");
            else if (!Path.IsPathRooted(configDirectory) || configDirectory.StartsWith("/") || configDirectory.StartsWith("\\"))
                configDirectory = Utils.GetMapPath(configDirectory);
            
            if (!string.IsNullOrWhiteSpace(filepath))
            {
                service = null;
                version = null;
                try
                {
                    filepath = filepath.ToLower();
                    if (filepath.Contains('/'))
                        filepath = filepath.Replace('/', '\\');
                    if (!string.IsNullOrWhiteSpace(basepath))
                    {
                        basepath = basepath.ToLower();
                        if (basepath.Contains('/'))
                            basepath = basepath.Replace('/', '\\');
                        if (!basepath.EndsWith("\\"))
                            basepath += "\\";
                        if (!Path.IsPathRooted(basepath) || basepath.StartsWith("\\"))
                        {
                            if (basepath.StartsWith("\\"))
                            {
                                basepath = Utils.PathCombine(false, configDirectory, basepath).ToLower();
                            }
                            else
                            {
                                Uri baseUri = new Uri(string.Format(@"{0}\{1}\{2}\", configDirectory, string.IsNullOrWhiteSpace(APIContext.Service) ? null : APIContext.Service.Replace('.', '\\'), APIContext.Version));
                                basepath = new Uri(baseUri, basepath).AbsolutePath.ToLower().Replace('/', '\\');
                            }
                        }
                    }
                    if (!Path.IsPathRooted(filepath) || filepath.StartsWith("\\"))
                    {
                        if (filepath.StartsWith("\\"))
                        {
                            filepath = Utils.PathCombine(false, string.IsNullOrWhiteSpace(basepath) ? configDirectory : basepath, filepath).ToLower();
                        }
                        else
                        {
                            Uri baseUri = new Uri(string.IsNullOrWhiteSpace(basepath) ? string.Format(@"{0}\{1}\{2}\", configDirectory, string.IsNullOrWhiteSpace(APIContext.Service) ? null : APIContext.Service.Replace('.', '\\'), APIContext.Version) : basepath);
                            filepath = new Uri(baseUri, filepath).AbsolutePath.ToLower().Replace('/', '\\');
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.Error(string.Format("Parse reference file path error: {0}-{1}", basepath, filepath), ex);
                    return null;
                }
                string pelativePath = filepath.Replace(configDirectory.ToLower(), string.Empty);
                string fileName = Path.GetFileName(pelativePath);
                apiname = Path.GetFileNameWithoutExtension(pelativePath);
                if (string.IsNullOrWhiteSpace(apiname))
                    return null;

                string directories = pelativePath.Replace(fileName, string.Empty).Trim('\\');
                if (directories.Length > 0)
                {
                    string v = null;
                    int lastSlashIndex = directories.LastIndexOf('\\');
                    if (lastSlashIndex > -1 && (v = directories.Substring(lastSlashIndex + 1)).Contains('.')) //判断最后一个目录包含点号，才认为是版本
                    {
                        service = directories.Substring(0, lastSlashIndex).Replace('\\', '.');
                        version = v;
                    }
                    else
                    {
                        service = directories.Replace('\\', '.');
                    }
                }
            }
            
            string blacklistDirectories = System.Configuration.ConfigurationManager.AppSettings["blacklistDirectories"];
            string blacklistVersions = System.Configuration.ConfigurationManager.AppSettings["blacklistVersions"];
            string blacklistApis = System.Configuration.ConfigurationManager.AppSettings["blacklistApis"];
            if (!string.IsNullOrWhiteSpace(blacklistDirectories))
            {
                if (blacklistDirectories.ToLower().Split(',').Contains(string.IsNullOrWhiteSpace(service) ? "/" : service.ToLower()))
                    return null;
            }
            if (!string.IsNullOrWhiteSpace(blacklistVersions) && !string.IsNullOrWhiteSpace(version))
            {
                if (blacklistVersions.ToLower().Split(',').Contains(version.ToLower()))
                    return null;
            }
            if (!string.IsNullOrWhiteSpace(blacklistApis))
            {
                if (blacklistApis.ToLower().Split(',').Contains(apiname.ToLower()))
                    return null;
            }
            if ((new string[] { "setting", "function", "log4net" }).Contains(apiname.ToLower()))
            {
                return null;
            }

            APIContext.Service = service;
            APIContext.Version = version;
            APIContext.ApiName = apiname;
            string apiFullName = APIContext.ApiFullName;
            if (!string.IsNullOrWhiteSpace(filepath) && APIContext.ExecuteChain.ContainsKey(apiFullName)) //防止死循环
                throw ExceptionHelper.GenException(ErrorCode.E000000008, filepath);

            StringBuilder servicePath = new StringBuilder();
            StringBuilder servicePath2 = new StringBuilder();
            string logConfigFile = null;
            string apiConfigString = null;
            DateTime lastWriteTime = DateTime.MinValue;

            bool apiConfigCache = false;
            string apiConfigCacheKey = string.Format("APIConfig_{0}", apiFullName).ToUpper();
            var baseConfigCacheModels = new List<ConfigCacheModel>()
            {
                new ConfigCacheModel()
                {
                    SettingKey = string.Format("APIConfig_Setting").ToUpper(),
                    FunctionKey = string.Format("APIConfig_Function").ToUpper()
                }
            };
            if (!string.IsNullOrWhiteSpace(service))
            {
                baseConfigCacheModels.AddRange(service.Split('.').Select(i =>
                {
                    if (servicePath.Length == 0)
                    {
                        servicePath.Append(i);
                        servicePath2.Append(i);
                    }
                    else
                    {
                        servicePath.AppendFormat(".{0}", i);
                        servicePath2.AppendFormat("\\{0}", i);
                    }
                    return new ConfigCacheModel()
                    {
                        ConfigPath = servicePath2.ToString(),
                        SettingKey = string.Format("APIConfig_{0}_Setting", servicePath).ToUpper(),
                        FunctionKey = string.Format("APIConfig_{0}_Function", servicePath).ToUpper(),
                    };
                }).ToList());
            }
            ICache configCacheHelper = null;
            try
            {
                var configCache = System.Configuration.ConfigurationManager.ConnectionStrings["ConfigCache"];
                if (configCache != null && !string.IsNullOrWhiteSpace(configCache.ProviderName))
                {
                    string adapter = configCache.ProviderName.IndexOf(",") > 0 ? configCache.ProviderName : string.Format("{0}.CacheHelper,{0}", configCache.ProviderName);
                    var sType = Type.GetType(adapter, false, true);
                    if (sType != null)
                    {
                        var sArgs = new List<object>();
                        if (!string.IsNullOrWhiteSpace(configCache.ConnectionString))
                            sArgs.Add(configCache.ConnectionString);
                        var sObject = Activator.CreateInstance(sType, sArgs.ToArray());
                        if (sObject != null && sObject is ICache)
                        {
                            configCacheHelper = (ICache)sObject;
                            apiConfigString = configCacheHelper.Get(apiConfigCacheKey) as string;
                            for (int i = 0; i < baseConfigCacheModels.Count; i++)
                            {
                                baseConfigCacheModels[i].SettingString = configCacheHelper.Get(baseConfigCacheModels[i].SettingKey) as string;
                                baseConfigCacheModels[i].FunctionString = configCacheHelper.Get(baseConfigCacheModels[i].FunctionKey) as string;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("Get api config from cache error", ex);
            }

            if (string.IsNullOrWhiteSpace(apiConfigString))
            {
                apiConfigCache = configCacheHelper != null;
                apiConfigString = Utils.LoadTextFile(Utils.PathCombine(false, configDirectory, servicePath2.ToString(), version, apiname + configExtension), out lastWriteTime);
            }

            var apiConfig = JSONHelper.JsonToJObject(apiConfigString);
            if (apiConfig != null)
            {
                if (apiConfig.ContainsProperty("lastWriteTime"))
                    lastWriteTime = apiConfig.GetJsonObjectDateTimeValue("lastWriteTime", DateTime.MinValue);
                else
                    apiConfig.Add("lastWriteTime", lastWriteTime);
                
                var mergeSettings = new JsonMergeSettings()
                {
                    MergeArrayHandling = MergeArrayHandling.Replace,
                    MergeNullValueHandling = MergeNullValueHandling.Ignore,
                    PropertyNameComparison = StringComparison.OrdinalIgnoreCase
                };
                var mergeSettings2 = new JsonMergeSettings()
                {
                    MergeArrayHandling = MergeArrayHandling.Union,
                    MergeNullValueHandling = MergeNullValueHandling.Ignore,
                    PropertyNameComparison = StringComparison.OrdinalIgnoreCase
                };
                JObject baseSetting = null;
                JObject baseFunction = null;
                for (int i = 0; i < baseConfigCacheModels.Count; i++)
                {
                    var model = baseConfigCacheModels[i];
                    if (string.IsNullOrWhiteSpace(model.SettingString))
                    {
                        model.SettingCache = configCacheHelper != null;
                        model.SettingString = Utils.LoadTextFile(Utils.PathCombine(false, configDirectory, model.ConfigPath, "setting" + configExtension));
                    }
                    var baseSettingObject = string.IsNullOrWhiteSpace(model.SettingString) || model.SettingString == "none" ? null : JSONHelper.JsonToJObject(model.SettingString);
                    if (baseSetting == null && baseSettingObject != null)
                        baseSetting = baseSettingObject;
                    else if (baseSettingObject != null)
                        baseSetting.Merge(baseSettingObject, mergeSettings);
                    if (model.SettingCache) //如果需要缓存，压缩配置
                        model.SettingString = baseSettingObject == null ? "none" : baseSettingObject.ToString(Formatting.None);

                    if (string.IsNullOrWhiteSpace(model.FunctionString))
                    {
                        model.FunctionCache = configCacheHelper != null;
                        model.FunctionString = Utils.LoadTextFile(Utils.PathCombine(false, configDirectory, model.ConfigPath, "function" + configExtension));
                    }
                    var baseFunctionObject = string.IsNullOrWhiteSpace(model.FunctionString) || model.FunctionString == "none" ? null : JSONHelper.JsonToJObject(model.FunctionString);
                    if (baseFunction == null && baseFunctionObject != null)
                        baseFunction = baseFunctionObject;
                    else if (baseFunctionObject != null)
                    {
                        string script = baseFunction.GetJsonObjectStringValue("script");
                        baseFunction.Merge(baseFunctionObject, mergeSettings2);
                        if (!string.IsNullOrWhiteSpace(script))
                            baseFunction.SetJsonObjectValue("script", string.Format("{0};{1}", script, baseFunction.GetJsonObjectStringValue("script")));
                    }
                    if (model.FunctionCache) //如果需要缓存，压缩配置
                        model.FunctionString = baseFunctionObject == null ? "none" : baseFunctionObject.ToString(Formatting.None);

                    if (string.IsNullOrWhiteSpace(filepath) && logConfigFile == null)
                    {
                        string path = baseConfigCacheModels[baseConfigCacheModels.Count - 1 - i].ConfigPath;
                        string logConfig = Utils.PathCombine(false, configDirectory, path, "log4net" + configExtension);
                        if (File.Exists(logConfig))
                        {
                            logConfigFile = logConfig;
                            APIContext.LogConfigFile = logConfigFile;
                            APIContext.LogRepositoryName = string.IsNullOrWhiteSpace(path) ? "log4net-trinity-api-repository" : string.Format("log4net-trinity-api-{0}-repository", path.Replace('\\', '-'));
                        }
                    }
                }

                if (configCacheHelper != null)
                {
                    try
                    {
                        if (apiConfigCache)
                            configCacheHelper.Set(apiConfigCacheKey, apiConfig.ToString(Formatting.None));
                        for (int i = 0; i < baseConfigCacheModels.Count; i++)
                        {
                            if (baseConfigCacheModels[i].SettingCache)
                                configCacheHelper.Set(baseConfigCacheModels[i].SettingKey, baseConfigCacheModels[i].SettingString);
                            if (baseConfigCacheModels[i].FunctionCache)
                                configCacheHelper.Set(baseConfigCacheModels[i].FunctionKey, baseConfigCacheModels[i].FunctionString);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogHelper.Error("Set api config to cache error", ex);
                    }
                }

                if (baseSetting != null)
                {
                    var constant = apiConfig.GetJsonObject("constant");
                    if (constant != null)
                    {
                        baseSetting.Merge(constant, mergeSettings);
                        apiConfig.SetJsonObjectValue("constant", baseSetting);
                    }
                    else
                    {
                        apiConfig.Add("constant", baseSetting);
                    }
                }

                if (baseFunction != null)
                {
                    var function = apiConfig.GetJsonObject("function");
                    if (function != null)
                    {
                        string script = baseFunction.GetJsonObjectStringValue("script");
                        baseFunction.Merge(function, mergeSettings2);
                        if (!string.IsNullOrWhiteSpace(script))
                            baseFunction.SetJsonObjectValue("script", string.Format("{0};{1}", script, baseFunction.GetJsonObjectStringValue("script")));
                        apiConfig.SetJsonObjectValue("function", baseFunction);
                    }
                    else
                    {
                        apiConfig.Add("function", baseFunction);
                    }
                }
                
                APIContext.ConfigLastModTime = lastWriteTime;
                APIContext.ExecuteChain.Add(apiFullName, APIContext.ExecuteChain.Count);
                return apiConfig;
            }
            return null;
        }

        /// <summary>
        /// 数据集合初始化
        /// </summary>
        /// <param name="settings">配置信息</param>
        /// <param name="header">HEAD参数</param>
        /// <param name="query">URL参数</param>
        /// <param name="request">API请求参数</param>
        /// <param name="uri">API请求URI信息</param>
        /// <param name="method">HTTP METHOD</param>
        /// <param name="rawQuery">URL原始参数</param>
        /// <param name="rawRequest">API请求原始参数</param>
        /// <returns>数据集合</returns>
        public static JObject DatasetInit(JObject settings, JToken header, JToken query, JToken request, JToken uri, string method, string rawQuery, string rawRequest)
        {
            var constant = settings.GetJsonObject("constant");
            var headerConfigs = settings.GetJsonObject("header");
            var queryConfigs = settings.GetJsonObject("query");
            var requestConfigs = settings.GetJsonObject("request");
            var responseConfigs = settings.GetJsonObject("response");
            var functionConfigs = settings.GetJsonObject("function");

            if (responseConfigs != null)
            {
                APIContext.LogResponse = responseConfigs.GetJsonObjectBooleanValue("logresponse", false);
                APIContext.ResponseContentType = responseConfigs.GetJsonObjectStringValue("contenttype");
            }

            if (functionConfigs != null)
            {
                var script = functionConfigs.GetNecessaryJsonObjectStringValue("script", "function");
                var assembly = functionConfigs.GetJsonObject("assembly");
                APIContext.ScriptEngine = new ScriptEngine(script, assembly);
            }

            var dataset = new JObject() { { "httpmethod", method?.ToUpper() }, { "requesturi", uri } };
            if (constant != null)
            {
                string lang = constant.GetJsonObjectStringValue("language")?.ToLower();
                if (!string.IsNullOrWhiteSpace(lang))
                    APIContext.Language = lang;
                APIContext.ErrorMessages = constant.GetJsonObject("errorMessage");
                APIContext.DefaultErrorMessages = constant.GetJsonObject("defaultErrorMessage");
                dataset.Add("constant", constant);
            }
            if (header != null && header.Type == JTokenType.Object)
            {
                string lang = (header as JObject).GetJsonObjectStringValue("language")?.ToLower();
                if (!string.IsNullOrWhiteSpace(lang))
                    APIContext.Language = lang;
                dataset.Add("header", header);
                header = dataset["header"];
            }
            if (query != null && query.Type == JTokenType.Object)
            {
                var queryJObject = query as JObject;
                if (queryJObject.ContainsProperty("filter"))
                    query.SetJsonObjectValue("filter", Utils.FilterConvert(JSONHelper.DeserializeObject<JToken>(queryJObject.GetJsonObjectStringValue("filter"))));

                if (queryJObject.ContainsProperty("sort"))
                    query.SetJsonObjectValue("sorts", JSONHelper.JsonToJArray(queryJObject.GetJsonObjectStringValue("sort")));

                string lang = queryJObject.GetJsonObjectStringValue("language")?.ToLower();
                if (!string.IsNullOrWhiteSpace(lang))
                    APIContext.Language = lang;

                dataset.Add("queryString", rawQuery);
                dataset.Add("query", query);
                query = dataset["query"];
            }
            if (request != null && request.Type != JTokenType.Null && request.Type != JTokenType.Undefined)
            {
                if (request.Type == JTokenType.Object)
                {
                    var requestJObject = request as JObject;
                    if (requestJObject.ContainsProperty("filter"))
                        request.SetJsonObjectValue("filter", Utils.FilterConvert(requestJObject.GetJsonObjectValue("filter")));

                    if (requestJObject.ContainsProperty("sort"))
                        request.SetJsonObjectValue("sort", requestJObject.GetJsonObjectValue("sort") as JArray);

                    string lang = requestJObject.GetJsonObjectStringValue("language")?.ToLower();
                    if (!string.IsNullOrWhiteSpace(lang))
                        APIContext.Language = lang;
                }
                
                dataset.Add("requestString", rawRequest);
                dataset.Add("request", request);
                request = dataset["request"];
            }

            if (headerConfigs != null)
            {
                header = CheckRequest("header", headerConfigs, header, dataset);
                dataset["header"] = header;
            }
            if (queryConfigs != null)
            {
                query = CheckRequest("query", queryConfigs, query, dataset);
                dataset["query"] = query;
            }
            if (requestConfigs != null)
            {
                APIContext.LogRequest = requestConfigs.GetJsonObjectBooleanValue("logrequest", true);
                request = CheckRequest("request", requestConfigs, request, dataset);
                dataset["request"] = request;
            }

            return dataset;
        }

        /// <summary>
        /// 执行API逻辑
        /// </summary>
        /// <param name="settings">配置信息</param>
        /// <param name="dataset">数据集合</param>
        /// <param name="ignoreAuth">是否忽略身份验证</param>
        /// <returns>数据集合</returns>
        public static JObject DoProcess(JObject settings, JObject dataset, bool ignoreAuth = false)
        {
            DateTime time = DateTime.Now;
            JObject authentication = dataset.GetJsonObject("constant").GetJsonObject("authentication");
            if (authentication != null && !ignoreAuth)
            {
                Authenticate(authentication, dataset);
                LogHelper.Debug(string.Format("{0} authenticate takes time:{1}ms", APIContext.ApiFullName, (DateTime.Now - time).TotalMilliseconds));
            }

            JObject linqs = null;
            var processes = settings.GetJsonObjectList("processes");
            if (processes != null && processes.Count > 0)
            {
                Func<string, int, JObject, JObject, JObject> Process = (type, i, process, ds) =>
                {
                    switch (type)
                    {
                        case "sql":
                            return ProcessSql(i, process, ds);
                        case "action":
                            return ProcessAction(i, process, ds);
                        case "api":
                            return ProcessApi(i, process, ds);
                        case "ref":
                            return ProcessRef(i, process, ds);
                        case "js":
                            return ProcessJs(i, process, ds);
                        case "cache":
                            return ProcessCache(i, process, ds);
                        default:
                            return null;
                    }
                };
                for (int i = 0; i < processes.Count; i++)
                {
                    time = DateTime.Now;
                    var process = processes[i];
                    var processPath = string.Format("process[{0}]", i);
                    var type = process.GetNecessaryJsonObjectStringValue("type", processPath).ToLower();
                    if (type == "linq")
                    {
                        linqs = ProcessLinq(i, process, dataset, linqs);
                    }
                    else
                    {
                        var async = process.ContainsProperty("async") ? ParseCondition(process.GetJsonObjectValue("async"), dataset) : false;
                        if (async.Type == JTokenType.Boolean && async.Value<bool>())
                        {
                            System.Threading.ThreadPool.UnsafeQueueUserWorkItem(new System.Threading.WaitCallback(o =>
                            {
                                try
                                {
                                    Process(type, i, process, dataset);
                                }
                                catch (Exception ex)
                                {
                                    LogHelper.Error(string.Format("{0} async running processes[{1}] {2} error", APIContext.ApiFullName, i, type), ex);
                                }
                            }), null);
                        }
                        else
                        {
                            dataset = Process(type, i, process, dataset);
                        }
                    }
                    LogHelper.Debug(string.Format("{0} running processes[{1}] {2} takes time:{3}ms", APIContext.ApiFullName, i, type, (DateTime.Now - time).TotalMilliseconds));
                }
            }

            LinqHelper.BuildLinq(linqs);

            return dataset;
        }

        #region Private Method
        /// <summary>
        /// 执行SQL
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>数据集合</returns>
        private static JObject ProcessSql(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var sqls = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("sqls", processPath);
            var sqlConnid = process.GetNecessaryJsonObjectStringValue("connid", processPath);
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");
            var connection = connections.GetNecessaryJsonObject(sqlConnid, "constant.connection");
            var isolation = process.GetJsonObjectStringValue("isolation");
            var transaction = process.GetJsonObjectBooleanValue("transaction", true);

            CheckProcessId(processid, dataset, processPath);

            var ds = new JObject();
            if (dataset != null)
            {
                foreach (var d in dataset)
                    ds.Add(d.Key, d.Value);
            }

            using (var dbBase = Utils.GetDBAdapter(sqlConnid, connection, isolation, transaction, false))
            {
                try
                {
                    for (int j = 0; j < sqls.Count; j++)
                    {
                        var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "sqls");
                        var sql = sqls[j];
                        var sqlType = sql.GetJsonObjectStringValue("type")?.ToLower();
                        var types = new List<string>() { "select", "insert", "update", "delete", "storedprocedure", "any" };
                        if (!types.Contains(sqlType))
                            throw new ConfigException("{0}.type must be one of {1}", processSubPath, string.Join("/", types));

                        var id = sql.GetJsonObjectStringValue("id");
                        var cmd = sql.GetJsonObjectStringValue("sql", sql.GetJsonObjectStringValue("cmd"));
                        var timeout = sql.GetJsonObjectIntValue("timeout", 30);
                        var ignoreRowsAffected = sql.GetJsonObjectBooleanValue("ignoreRowsAffected");

                        List<DBParameter> conditionParameters = new List<DBParameter>();
                        var condition = sql.ContainsProperty("if") ? ParseCondition(sql.GetJsonObjectValue("if"), ds) : true;
                        if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                            continue;
                        else if (condition.Type == JTokenType.String) //是SQL脚本
                        {
                            var dbcommand = ParseSqlString(dbBase, condition.ToString(), ds);
                            if (dbcommand == null)
                                continue;
                            else
                            {
                                if (dbcommand.Parameters != null && dbcommand.Parameters.Count > 0)
                                    conditionParameters.AddRange(dbcommand.Parameters);
                                int cmdresult = TypeConverter.ObjectToInt(dbBase.ExecuteScalar(dbcommand.Sql, conditionParameters, CommandType.Text, timeout));
                                if (cmdresult <= 0)
                                    continue;
                            }
                        }

                        if (sqlType == "select")
                        {
                            if (string.IsNullOrWhiteSpace(processid))
                                CheckProcessId(id, ds, processSubPath);
                            
                            var cacheManager = CacheManager.Init(sql.GetJsonObject("cache"), connections, ds, processSubPath + ".cache");
                            var result = cacheManager?.Get<List<JArray>>();
                            if (result == null)
                            {
                                List<DBParameter> parameters = null;
                                var dbcommand = ParseSqlString(dbBase, cmd, ds);
                                if (dbcommand != null)
                                {
                                    cmd = dbcommand.Sql;
                                    parameters = dbcommand.Parameters;
                                }
                                if (string.IsNullOrWhiteSpace(cmd))
                                    throw new ConfigException("{0}.cmd is null or empty", processSubPath);
                                var pagination = sql.GetJsonObject("pagination");
                                if (pagination != null)
                                {
                                    cmd = BuildPagingSql(dbBase, cmd, pagination, ds);
                                    string frontCmd = pagination.GetJsonObjectStringValue("frontCmd");
                                    var frontCommand = ParseSqlString(dbBase, frontCmd, ds);
                                    if (frontCommand != null && !string.IsNullOrWhiteSpace(frontCommand.Sql))
                                    {
                                        if (frontCommand.Parameters != null && frontCommand.Parameters.Count > 0)
                                        {
                                            if (parameters == null)
                                                parameters = new List<DBParameter>();
                                            foreach (var param in frontCommand.Parameters)
                                            {
                                                if (!parameters.Exists(p => p.ParameterName == param.ParameterName))
                                                    parameters.Add(param);
                                            }
                                        }
                                        cmd = string.Format("{0};{1}", frontCommand.Sql, cmd);
                                    }
                                }
                                if (string.IsNullOrWhiteSpace(cmd))
                                    throw new ConfigException("{0} fail to build paging sql", processSubPath);

                                DataTableCollection datatables = dbBase.Query(cmd, parameters, CommandType.Text, timeout).Tables;

                                if (datatables != null && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                                {
                                    result = new List<JArray>();
                                    for (int k = 0; k < datatables.Count; k++)
                                    {
                                        var datajson = JSONHelper.DataTableToJArray(datatables[k]);
                                        if (datajson != null)
                                            result.Add(datajson);
                                    }
                                    cacheManager?.Set(result);
                                }
                            }

                            if (result != null)
                            {
                                if (!string.IsNullOrWhiteSpace(processid))
                                {
                                    JArray dataList = ds.GetJsonObjectValue(processid) as JArray;
                                    if (dataList == null)
                                    {
                                        dataList = new JArray();
                                        ds.Add(processid, dataList);
                                    }
                                    for (int k = 0; k < result.Count; k++)
                                    {
                                        dataList.Add(result[k]);
                                    }
                                }
                                else if (!string.IsNullOrWhiteSpace(id))
                                {
                                    for (int k = 0; k < result.Count; k++)
                                    {
                                        var key = k == 0 ? id : id + k;
                                        ds.Add(key, result[k]);
                                    }
                                }
                            }
                        }
                        else if (sqlType == "storedprocedure")
                        {
                            if (string.IsNullOrWhiteSpace(processid))
                                CheckProcessId(id, ds, processSubPath);

                            var cacheManager = CacheManager.Init(sql.GetJsonObject("cache"), connections, ds, processSubPath + ".cache");
                            var result = cacheManager?.Get<JObject>();
                            if (result == null)
                            {
                                if (string.IsNullOrWhiteSpace(cmd))
                                    throw new ConfigException("{0}.cmd is null or empty", processSubPath);

                                var hasOutput = false;
                                var psParameters = sql.GetJsonObjectValue("parameters");
                                var parameters = GetStoredProcedureParameter(psParameters, ds, out hasOutput);

                                dbBase.ExecuteNonQuery(cmd, parameters, CommandType.StoredProcedure, timeout, hasOutput);

                                if (hasOutput && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                                {
                                    result = new JObject();
                                    for (int k = 0; k < parameters.Count; k++)
                                    {
                                        var param = parameters[k];
                                        if (param.Direction == ParameterDirection.InputOutput || param.Direction == ParameterDirection.Output || param.Direction == ParameterDirection.ReturnValue)
                                        {
                                            if (param.Value != null && param.Value != DBNull.Value)
                                            {
                                                JValue value = null;
                                                if (param.Direction == ParameterDirection.ReturnValue)
                                                    value = new JValue(Convert.ToInt32(param.Value));
                                                else
                                                    value = new JValue(param.Value);
                                                result.SetJsonObjectValue(param.ParameterName, value);
                                            }
                                            else
                                            {
                                                result.SetJsonObjectValue(param.ParameterName, null);
                                            }
                                        }
                                    }
                                    cacheManager?.Set(result);
                                }
                            }

                            if (result != null)
                            {
                                if (!string.IsNullOrWhiteSpace(processid))
                                {
                                    JArray dataList = ds.GetJsonObjectValue(processid) as JArray;
                                    if (dataList == null)
                                    {
                                        dataList = new JArray();
                                        ds.Add(processid, dataList);
                                    }
                                    dataList.Add(result);
                                }
                                else if (!string.IsNullOrWhiteSpace(id))
                                {
                                    ds.Add(id, result);
                                }
                            }
                        }
                        else
                        {
                            if (string.IsNullOrWhiteSpace(processid))
                                CheckProcessId(id, ds, processSubPath);

                            List<DBCommand> subCmds = null;
                            if (!string.IsNullOrWhiteSpace(cmd))
                            {
                                var dbcommand = ParseSqlString(dbBase, cmd, ds);
                                if (dbcommand != null)
                                {
                                    subCmds = new List<DBCommand>();
                                    subCmds.Add(dbcommand);
                                }
                            }
                            else
                            {
                                var cmdConfig = sql.GetJsonObjectValue("cmd");
                                if (cmdConfig == null)
                                    throw new ConfigException("{0}.cmd must be a string or an object", processSubPath);

                                var cmdObject = Utils.ParseObject(cmdConfig, ds, true);
                                if (cmdObject == null)
                                    throw new ConfigException("{0}.cmd must be a string or an object", processSubPath);

                                subCmds = GetSqlCommands(cmdObject, ds, dbBase, sqlType);
                            }

                            var result = ExecuteSqlCommands(subCmds, dbBase, timeout, sqlType, ignoreRowsAffected);
                            if (result != null)
                            {
                                if (!string.IsNullOrWhiteSpace(processid))
                                {
                                    JArray dataList = ds.GetJsonObjectValue(processid) as JArray;
                                    if (dataList == null)
                                    {
                                        dataList = new JArray();
                                        ds.Add(processid, dataList);
                                    }
                                    dataList.Add(result);
                                }
                                else if (!string.IsNullOrWhiteSpace(id))
                                {
                                    ds.Add(id, result);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    dbBase.Rollback();
                    if (ex.Message.StartsWith("@"))
                    {
                        var msg = ex.Message.Split('\n').ToList();
                        if (msg.Count > 1)
                            throw ExceptionHelper.GenException(msg[0], msg.GetRange(1, msg.Count - 1).ToArray());
                        else
                            throw ExceptionHelper.GenException(msg[0]);
                    }
                    throw ex;
                }
            }; //提交事务并且关闭连接

            if (dataset != null)
            {
                foreach (var item in ds) //数据库事务提交后，再将数据合并到主数据集合
                {
                    if (!dataset.ContainsProperty(item.Key))
                        dataset.Add(item.Key, item.Value);
                }
            }

            return dataset;
        }

        /// <summary>
        /// 执行扩展逻辑
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>数据集合</returns>
        private static JObject ProcessAction(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var actions = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("actions", processPath);
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");
            var dllName = process.GetJsonObjectStringValue("dllname");
            if (!string.IsNullOrWhiteSpace(dllName) && dllName.ToLower().EndsWith(".dll"))
                dllName = dllName.Substring(0, dllName.Length - 4);

            CheckProcessId(processid, dataset, processPath);

            for (int j = 0; j < actions.Count; j++)
            {
                var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "actions");
                var action = actions[j];
                var id = action.GetJsonObjectStringValue("id");
                
                var condition = action.ContainsProperty("if") ? ParseCondition(action.GetJsonObjectValue("if"), dataset) : true;
                if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                    continue;

                if (string.IsNullOrWhiteSpace(processid))
                    CheckProcessId(id, dataset, processSubPath);

                var cacheManager = CacheManager.Init(action.GetJsonObject("cache"), connections, dataset, processSubPath + ".cache");
                var result = cacheManager?.Get<JToken>();
                if (result == null)
                {
                    var classname = action.GetNecessaryJsonObjectStringValue("classname", processSubPath);
                    string actionAdapter = !string.IsNullOrWhiteSpace(dllName) ? string.Format("{0},{1}", classname, dllName) : classname;
                    var cType = Type.GetType(actionAdapter, false, true);
                    if (cType == null)
                        throw new ConfigException("{0} action library not exist", processSubPath);

                    var cObject = Activator.CreateInstance(cType);
                    if (cObject == null || !(cObject is IAction))
                        throw new ConfigException("{0} action library not exist", processSubPath);

                    List<JToken> paramList = new List<JToken>();
                    if (action.ContainsProperty("args"))
                    {
                        var args = action.GetJsonObjectValue("args");
                        var arguments = Utils.ParseObject(args, dataset);
                        if (arguments != null && arguments.Type == JTokenType.Array)
                        {
                            foreach (JToken arg in (JArray)arguments)
                                paramList.Add(arg);
                        }
                        else
                        {
                            paramList.Add(arguments);
                        }
                    }

                    result = ((IAction)cObject).Invoke(dataset, paramList.ToArray());

                    if (result != null && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                    {
                        cacheManager?.Set(result);
                    }
                }

                if (result != null && dataset != null)
                {
                    if (!string.IsNullOrWhiteSpace(processid))
                    {
                        JArray dataList = dataset.GetJsonObjectValue(processid) as JArray;
                        if (dataList == null)
                        {
                            dataList = new JArray();
                            dataset.Add(processid, dataList);
                        }
                        dataList.Add(result);
                    }
                    else if (!string.IsNullOrWhiteSpace(id))
                    {
                        dataset.Add(id, result);
                    }
                }
            }

            return dataset;
        }

        /// <summary>
        /// 调用API
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>数据集合</returns>
        private static JObject ProcessApi(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var apis = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("apis", processPath);
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");
            var domain = ParseUrlString(process.GetJsonObjectStringValue("domain")?.ToLower() ?? string.Empty, dataset);
            if (!domain.StartsWith("http://") && !domain.StartsWith("https://"))
                domain = string.Empty;

            CheckProcessId(processid, dataset, processPath);

            for (int j = 0; j < apis.Count; j++)
            {
                var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "apis");
                var api = apis[j];
                var id = api.GetJsonObjectStringValue("id");

                var condition = api.ContainsProperty("if") ? ParseCondition(api.GetJsonObjectValue("if"), dataset) : true;
                if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                    continue;

                if (string.IsNullOrWhiteSpace(processid))
                    CheckProcessId(id, dataset, processSubPath);

                var cacheManager = CacheManager.Init(api.GetJsonObject("cache"), connections, dataset, processSubPath + ".cache");
                var result = cacheManager?.Get<JToken>();
                if (result == null)
                {
                    var url = api.GetNecessaryJsonObjectStringValue("url", processSubPath);

                    if (!string.IsNullOrWhiteSpace(domain))
                        url = Utils.PathCombine(domain, url);

                    var method = api.GetJsonObjectStringValue("method");
                    var timeout = api.GetJsonObjectIntValue("timeout", 30);
                    var apiHeader = Utils.ParseObject(api.GetJsonObjectValue("header"), dataset);
                    var apiRequest = Utils.ParseObject(api.GetJsonObjectValue("request"), dataset);
                    var apiRequests = Utils.ParseObject(api.GetJsonObjectValue("requests"), dataset) as JArray;
                    var apiResponse = api.GetJsonObjectValue("response");
                    var apiResponseConfig = api.GetJsonObject("config");

                    if (apiRequests != null && apiRequests.Count > 0)
                    {
                        var results = new JArray();
                        foreach (var request in apiRequests)
                        {
                            results.Add(HttpApiRequest(url, method, timeout, apiHeader, request, apiResponse, apiResponseConfig, dataset));
                        }
                        result = results;
                    }
                    else
                    {
                        result = HttpApiRequest(url, method, timeout, apiHeader, apiRequest, apiResponse, apiResponseConfig, dataset);
                    }

                    if (result != null && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                    {
                        cacheManager?.Set(result);
                    }
                }

                if (result != null && dataset != null)
                {
                    if (!string.IsNullOrWhiteSpace(processid))
                    {
                        JArray dataList = dataset.GetJsonObjectValue(processid) as JArray;
                        if (dataList == null)
                        {
                            dataList = new JArray();
                            dataset.Add(processid, dataList);
                        }
                        dataList.Add(result);
                    }
                    else if (!string.IsNullOrWhiteSpace(id))
                    {
                        dataset.Add(id, result);
                    }
                }
            }

            return dataset;
        }

        /// <summary>
        /// 执行API文件引用逻辑
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>数据集合</returns>
        private static JObject ProcessRef(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var refs = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("refs", processPath);
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");
            var basePath = ParseUrlString(process.GetJsonObjectStringValue("basePath")?.ToLower() ?? string.Empty, dataset);

            CheckProcessId(processid, dataset, processPath);
        
            bool LogRequest = APIContext.LogRequest;
            bool LogResponse = APIContext.LogResponse;
            string LogRepositoryName = APIContext.LogRepositoryName;
            string LogConfigFile = APIContext.LogConfigFile;
            string ResponseContentType = APIContext.ResponseContentType;
            JObject ResponseHeaders = APIContext.ResponseHeaders;
            string Service = APIContext.Service;
            string Version = APIContext.Version;
            string ApiName = APIContext.ApiName;
            string Language = APIContext.Language;
            DateTime ConfigLastModTime = APIContext.ConfigLastModTime;
            JObject ErrorMessages = APIContext.ErrorMessages;
            JObject DefaultErrorMessages = APIContext.DefaultErrorMessages;
            ScriptEngine ScriptEngine = APIContext.ScriptEngine;

            for (int j = 0; j < refs.Count; j++)
            {
                var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "refs");
                var reference = refs[j];
                var id = reference.GetJsonObjectStringValue("id");

                var condition = reference.ContainsProperty("if") ? ParseCondition(reference.GetJsonObjectValue("if"), dataset) : true;
                if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                    continue;

                if (string.IsNullOrWhiteSpace(processid))
                    CheckProcessId(id, dataset, processSubPath);

                var cacheManager = CacheManager.Init(reference.GetJsonObject("cache"), connections, dataset, processSubPath + ".cache");
                var result = cacheManager?.Get<JToken>();
                if (result == null)
                {
                    var src = ParseUrlString(reference.GetNecessaryJsonObjectStringValue("src", processSubPath), dataset);

                    var settings = APIInit(null, null, null, src, basePath);
                    if (settings == null)
                        throw ExceptionHelper.GenException(ErrorCode.E000000009, src);

                    var method = reference.GetJsonObjectStringValue("method");
                    var refUri = Utils.ParseObject(reference.GetJsonObjectValue("uri"), dataset) ?? dataset.GetJsonObjectValue("requesturi");
                    var refHeader = Utils.ParseObject(reference.GetJsonObjectValue("header"), dataset);
                    var refQuery = Utils.ParseObject(reference.GetJsonObjectValue("query"), dataset);
                    var refRequest = Utils.ParseObject(reference.GetJsonObjectValue("request"), dataset);
                    var refRequests = Utils.ParseObject(reference.GetJsonObjectValue("requests"), dataset) as JArray;
                    var refResponse = reference.GetJsonObjectValue("response");
                    var refResponseConfig = reference.GetJsonObject("config");
                    var rawQuery = ParseUrlString(reference.GetJsonObjectStringValue("queryString"), dataset);
                    var rawRequest = ParseUrlString(reference.GetJsonObjectStringValue("requestString"), dataset);

                    if (refRequests != null && refRequests.Count > 0)
                    {
                        var results = new JArray();
                        foreach (var request in refRequests)
                        {
                            results.Add(RunReference(settings, refHeader, refQuery, request, refUri, method, rawQuery, rawRequest, Language, DefaultErrorMessages, ErrorMessages, refResponse, refResponseConfig, dataset));
                        }
                        result = results;
                    }
                    else
                    {
                        result = RunReference(settings, refHeader, refQuery, refRequest, refUri, method, rawQuery, rawRequest, Language, DefaultErrorMessages, ErrorMessages, refResponse, refResponseConfig, dataset);
                    }

                    if (result != null && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                    {
                        cacheManager?.Set(result);
                    }
                }

                if (result != null && dataset != null)
                {
                    if (!string.IsNullOrWhiteSpace(processid))
                    {
                        JArray dataList = dataset.GetJsonObjectValue(processid) as JArray;
                        if (dataList == null)
                        {
                            dataList = new JArray();
                            dataset.Add(processid, dataList);
                        }
                        dataList.Add(result);
                    }
                    else if (!string.IsNullOrWhiteSpace(id))
                    {
                        dataset.Add(id, result);
                    }
                }
            }

            APIContext.LogRequest = LogRequest;
            APIContext.LogResponse = LogResponse;
            APIContext.LogRepositoryName = LogRepositoryName;
            APIContext.LogConfigFile = LogConfigFile;
            APIContext.ResponseContentType = ResponseContentType;
            APIContext.ResponseHeaders = ResponseHeaders;
            APIContext.Service = Service;
            APIContext.Version = Version;
            APIContext.ApiName = ApiName;
            APIContext.Language = Language;
            APIContext.ConfigLastModTime = ConfigLastModTime;
            APIContext.ErrorMessages = ErrorMessages;
            APIContext.DefaultErrorMessages = DefaultErrorMessages;
            APIContext.ScriptEngine = ScriptEngine;

            return dataset;
        }

        /// <summary>
        /// 执行JS逻辑
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>数据集合</returns>
        private static JObject ProcessJs(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var funcs = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("funcs", processPath);
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");
            var script = APIContext.ScriptEngine == null ? process.GetNecessaryJsonObjectStringValue("script", processPath) : process.GetJsonObjectStringValue("script");
            var assembly = process.GetJsonObject("assembly");

            CheckProcessId(processid, dataset, processPath);

            using (var engine = APIContext.ScriptEngine == null ? new ScriptEngine(script, assembly) : APIContext.ScriptEngine.NewInstance(script, assembly))
            {
                for (int j = 0; j < funcs.Count; j++)
                {
                    var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "funcs");
                    var function = funcs[j];
                    var id = function.GetJsonObjectStringValue("id");

                    var condition = function.ContainsProperty("if") ? ParseCondition(function.GetJsonObjectValue("if"), dataset) : true;
                    if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                        continue;

                    if (string.IsNullOrWhiteSpace(processid))
                        CheckProcessId(id, dataset, processSubPath);

                    var cacheManager = CacheManager.Init(function.GetJsonObject("cache"), connections, dataset, processSubPath + ".cache");
                    var result = cacheManager?.Get<JToken>();
                    if (result == null)
                    {
                        var func = function.GetNecessaryJsonObjectStringValue("func", processSubPath);
                        var argType = function.GetJsonObjectIntValue("argType");

                        List<object> paramList = new List<object>();
                        if (function.ContainsProperty("args"))
                        {
                            var args = function.GetJsonObjectValue("args");
                            object arguments = argType == 1 || argType == 2 ? Utils.ParseObject(args, dataset) : Utils.GetJSAarguments(Utils.ParseObject(args, dataset));
                            if (args != null && args.Type == JTokenType.Array && arguments is IList)
                            {
                                foreach (object arg in (IList)arguments)
                                    paramList.Add(argType == 1 && arg is JToken ? ((JToken)arg).ToString(Formatting.None) : arg);
                            }
                            else
                            {
                                paramList.Add(argType == 1 && arguments is JToken ? ((JToken)arguments).ToString(Formatting.None) : arguments);
                            }
                        }
                        try
                        {
                            if (argType == 1 && paramList.Count > 0)
                                result = LinqHelper.ToJToken(engine.Evaluate(string.Format("{0}({1})", func, string.Join(",", paramList))));
                            else
                                result = LinqHelper.ToJToken(engine.Invoke(func, paramList.ToArray()));
                        }
                        catch (Exception ex)
                        {
                            if (ex.Message.StartsWith("@"))
                            {
                                var msg = ex.Message.Split('\n').ToList();
                                if (msg.Count > 1)
                                    throw ExceptionHelper.GenException(msg[0], msg.GetRange(1, msg.Count - 1).ToArray());
                                else
                                    throw ExceptionHelper.GenException(msg[0]);
                            }
                            throw ex;
                        }

                        if (result != null && (!string.IsNullOrWhiteSpace(processid) || !string.IsNullOrWhiteSpace(id)))
                        {
                            cacheManager?.Set(result);
                        }
                    }

                    if (result != null && dataset != null)
                    {
                        if (!string.IsNullOrWhiteSpace(processid))
                        {
                            JArray dataList = dataset.GetJsonObjectValue(processid) as JArray;
                            if (dataList == null)
                            {
                                dataList = new JArray();
                                dataset.Add(processid, dataList);
                            }
                            dataList.Add(result);
                        }
                        else if (!string.IsNullOrWhiteSpace(id))
                        {
                            dataset.Add(id, result);
                        }
                    }
                }
            }

            return dataset;
        }

        /// <summary>
        /// 缓存操作
        /// </summary>
        /// <param name="index"></param>
        /// <param name="process"></param>
        /// <param name="dataset"></param>
        /// <returns></returns>
        private static JObject ProcessCache(int index, JObject process, JObject dataset)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var caches = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("caches", processPath);
            var connid = process.GetJsonObjectStringValue("connid");
            var connections = dataset.GetJsonObject("constant").GetJsonObject("connection");

            CheckProcessId(processid, dataset, processPath);

            for (int j = 0; j < caches.Count; j++)
            {
                var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "caches");
                var cache = caches[j];
                var id = cache.GetJsonObjectStringValue("id");
                var type = cache.GetJsonObjectStringValue("type")?.ToLower();
                var types = new List<string>() { "get", "set", "remove", "clear" };
                if (!types.Contains(type))
                    throw new ConfigException("{0}.type must be one of {1}", processSubPath, string.Join("/", types));

                var condition = cache.ContainsProperty("if") ? ParseCondition(cache.GetJsonObjectValue("if"), dataset) : true;
                if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                    continue;

                if (string.IsNullOrWhiteSpace(processid))
                    CheckProcessId(id, dataset, processSubPath);

                if (!cache.ContainsProperty("connid") && !string.IsNullOrWhiteSpace(connid))
                    cache.SetJsonObjectValue("connid", connid);

                if (type == "clear")
                {
                    cache.SetJsonObjectValue("key", "clear"); // 随便设置一个key，否则没有key，下面Init会报错
                    var cacheManager = CacheManager.Init(cache, connections, dataset, processSubPath);
                    cacheManager.Clear();
                }
                else
                {
                    var cacheManager = CacheManager.Init(cache, connections, dataset, processSubPath);
                    if (type == "get")
                    {
                        var cacheData = cacheManager.Get<JToken>();
                        if (cacheData != null && dataset != null)
                        {
                            if (!string.IsNullOrWhiteSpace(processid))
                            {
                                JArray dataList = dataset.GetJsonObjectValue(processid) as JArray;
                                if (dataList == null)
                                {
                                    dataList = new JArray();
                                    dataset.Add(processid, dataList);
                                }
                                dataList.Add(cacheData);
                            }
                            else if (!string.IsNullOrWhiteSpace(id))
                            {
                                dataset.Add(id, cacheData);
                            }
                        }
                    }
                    else if (type == "set")
                    {
                        var value = cache.GetNecessaryJsonObjectValue("value", processSubPath);
                        var valueObject = Utils.ParseObject(value, dataset);
                        cacheManager.Set(valueObject);
                    }
                    else if (type == "remove")
                    {
                        cacheManager.Remove();
                    }
                }
            }

            return dataset;
        }

        /// <summary>
        /// 组装Linq
        /// </summary>
        /// <param name="index">序号</param>
        /// <param name="process">配置</param>
        /// <param name="dataset">数据集合</param>
        /// <param name="linqCollection">linq集合</param>
        /// <returns>Linq集合</returns>
        private static JObject ProcessLinq(int index, JObject process, JObject dataset, JObject linqCollection)
        {
            var processPath = string.Format("processes[{0}]", index);
            var processid = process.GetJsonObjectStringValue("id");
            var linqs = process.GetJsonObjectList("cmds") ?? process.GetNecessaryJsonObjectList("linqs", processPath);
            var linqParameters = dataset.GetJsonObject("linqs");

            if (linqCollection == null)
                linqCollection = new JObject();
            if (linqParameters == null)
                linqParameters = new JObject();

            if (!string.IsNullOrWhiteSpace(processid))
            {
                CheckProcessId(processid, dataset, processPath);

                if (linqCollection.ContainsProperty(processid))
                    throw new ConfigException("{0}.id is duplicate", processPath);
            }

            for (int j = 0; j < linqs.Count; j++)
            {
                var processSubPath = string.Format("{0}.{2}[{1}]", processPath, j, process.ContainsProperty("cmds") ? "cmds" : "linqs");
                var linq = linqs[j];
                var id = string.Empty;
                var cmd = linq.GetJsonObjectStringValue("linq", linq.GetNecessaryJsonObjectStringValue("cmd", processSubPath));

                var condition = linq.ContainsProperty("if") ? ParseCondition(linq.GetJsonObjectValue("if"), dataset) : true;

                if (string.IsNullOrWhiteSpace(processid))
                {
                    id = linq.GetNecessaryJsonObjectStringValue("id", processSubPath);

                    CheckProcessId(id, dataset, processSubPath);

                    if (linqCollection.ContainsProperty(id))
                        throw new ConfigException("{0}.id is duplicate", processSubPath);
                }

                var command = ParseLinqString(cmd, dataset);
                if (command != null && dataset != null)
                {
                    if (!string.IsNullOrWhiteSpace(processid))
                    {
                        JArray linqCollectionList = linqCollection.GetJsonObjectValue(processid) as JArray;
                        if (linqCollectionList == null)
                        {
                            linqCollectionList = new JArray();
                            linqCollection.Add(processid, linqCollectionList);
                        }
                        JArray linqParametersList = linqParameters.GetJsonObjectValue(processid) as JArray;
                        if (linqParametersList == null)
                        {
                            linqParametersList = new JArray();
                            linqParameters.Add(processid, linqParametersList);
                        }
                        linqCollectionList.Add(command.Linq);
                        if (condition.Type != JTokenType.Boolean || condition.Value<bool>())
                            linqParametersList.Add(new JObject() { { "index", j.ToString() }, { "params", JToken.FromObject(command.Parameters) } });
                    }
                    else
                    {
                        linqCollection.Add(id, command.Linq);
                        if (condition.Type != JTokenType.Boolean || condition.Value<bool>())
                            linqParameters.Add(id, new JObject() { { "index", j.ToString() }, { "params", JToken.FromObject(command.Parameters) } });
                    }
                }
            }
            dataset.SetJsonObjectValue("linqs", linqParameters);

            return linqCollection;
        }

        /// <summary>
        /// 身份验证
        /// </summary>
        /// <param name="config">配置信息</param>
        /// <param name="dataset">数据集合</param>
        private static void Authenticate(JObject config, JObject dataset)
        {
            var processPath = "constant.authentication";
            var dllName = config.GetJsonObjectStringValue("dllname");
            if (!string.IsNullOrWhiteSpace(dllName) && dllName.ToLower().EndsWith(".dll"))
                dllName = dllName.Substring(0, dllName.Length - 4);
            var classname = config.GetNecessaryJsonObjectStringValue("classname", processPath);
            string actionAdapter = !string.IsNullOrWhiteSpace(dllName) ? string.Format("{0},{1}", classname, dllName) : classname;
            var cType = Type.GetType(actionAdapter, false, true);
            if (cType == null)
                throw new ConfigException("{0} action library not exist", processPath);

            var cObject = Activator.CreateInstance(cType);
            if (cObject == null || !(cObject is IAction))
                throw new ConfigException("{0} action library not exist", processPath);

            ((IAction)cObject).Invoke(dataset);
        }

        /// <summary>
        /// 校验参数是否合法
        /// </summary>
        /// <param name="path">参数的路径</param>
        /// <param name="config">校验配置</param>
        /// <param name="param">参数值</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>校验结果值</returns>
        private static JToken CheckRequest(string path, JObject config, JToken param, JObject dataset)
        {
            if (config == null)
                return param;

            var dataformat = config.GetJsonObjectValue("dataformat");
            //if (dataformat == null)
            //    throw new ConfigException(string.Format("{0}.dataformat", path));

            //JObject dataset = (parent?.Root ?? param?.Root) as JObject;
            var errormsgs = config.GetJsonObject("errormsgs");
            var mandatory = config.ContainsProperty("mandatory") ? ParseCondition(config.GetJsonObjectValue("mandatory"), dataset) : false;
            var addToFilter = config.GetJsonObjectValue("addToFilter");
            var range = config.GetJsonObjectValue("range");
            JToken min = config.GetJsonObjectValue("min");
            JToken max = config.GetJsonObjectValue("max");
            JToken defaultvalue = config.GetJsonObjectValue("defaultvalue");

            bool isEmpty = param == null || param.Type == JTokenType.Null || param.Type == JTokenType.Undefined || (param.Type == JTokenType.String && param.ToString().Length == 0) || (param.Type == JTokenType.Array && !param.HasValues) || (param.Type == JTokenType.Object && !param.HasValues);

            if (isEmpty)
            {
                if (mandatory.Type == JTokenType.Boolean && mandatory.Value<bool>())
                    throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("empty"), ErrorCode.E000000021, path);
                if (defaultvalue != null)
                    param = Utils.ParseObject(defaultvalue, dataset);
                if (param == null || param.Type == JTokenType.Null || param.Type == JTokenType.Undefined)
                {
                    TryAddToFilter(path, addToFilter, param, dataset);
                    return param;
                }
            }

            if (dataformat != null && dataformat.Type != param.Type)
            {
                bool err = true;
                switch (dataformat.Type)
                {
                    case JTokenType.String:
                        if (param is JValue)
                        {
                            param = param.Type == JTokenType.Date ? ((JValue)param).ToString(@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK") : param.ToString();
                            err = false;
                        }
                        break;
                    case JTokenType.Integer:
                        int value1 = 0;
                        if (param is JValue && int.TryParse(param.ToString(), out value1))
                        {
                            param = value1;
                            err = false;
                        }
                        break;
                    case JTokenType.Float:
                        decimal value2 = 0;
                        if (param is JValue && decimal.TryParse(param.ToString(), out value2))
                        {
                            param = value2;
                            err = false;
                        }
                        break;
                    case JTokenType.Date:
                        DateTime value3 = DateTime.MinValue;
                        if (param is JValue && DateTime.TryParse(param.ToString(), out value3))
                        {
                            param = value3;
                            err = false;
                        }
                        break;
                }
                if (err)
                    throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("typeinvalid"), ErrorCode.E000000022, path, dataformat.Type.ToString().ToLower());
            }

            if (dataformat == null)
            {
                range = null;
                min = null;
                max = null;
            }
            else if (param.Type != JTokenType.Object)
            {
                range = Utils.ParseObject(range, dataset);
                min = Utils.ParseObject(min, dataset);
                max = Utils.ParseObject(max, dataset);
            }

            if (param.Type == JTokenType.Object)
            {
                var dataformatObject = dataformat as JObject;
                if (dataformatObject != null && dataformatObject.HasValues)
                {
                    var paramObject = param as JObject;
                    foreach (var item in dataformatObject)
                    {
                        var subConfig = dataformatObject.GetJsonObject(item.Key);
                        var subParam = paramObject.GetJsonObjectValue(item.Key);
                        paramObject.SetJsonObjectValue(item.Key, CheckRequest(string.Format("{0}.{1}", path, item.Key), subConfig, subParam, dataset));
                    }
                }
            }
            else if (param.Type == JTokenType.Array)
            {
                var paramArray = param as JArray;
                if (min != null && min.Type == JTokenType.Integer)
                {
                    int minCount = min.Value<int>();
                    if (paramArray.Count < minCount)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("less"), ErrorCode.E000000023, path, minCount);
                }
                if (max != null && max.Type == JTokenType.Integer)
                {
                    int maxCount = max.Value<int>();
                    if (paramArray.Count > maxCount)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("excess"), ErrorCode.E000000024, path, maxCount);
                }

                if (dataformat != null && dataformat.HasValues)
                {
                    var dataformatFirst = dataformat.FirstOrDefault();
                    for (int i = 0; i < paramArray.Count; i++)
                    {
                        var pm = paramArray[i];
                        if (pm.Type != dataformatFirst.Type)
                        {
                            string itemTypeInvalid = errormsgs.GetJsonObjectStringValue("itemtypeinvalid");
                            if (string.IsNullOrWhiteSpace(itemTypeInvalid))
                                itemTypeInvalid = errormsgs.GetJsonObjectStringValue("typeinvalid");
                            throw ExceptionHelper.GenException(itemTypeInvalid, ErrorCode.E000000022, string.Format("{0}[{1}]", path, i), dataformatFirst.Type.ToString().ToLower());
                        }
                        if (dataformatFirst.Type == JTokenType.Object)
                        {
                            var dataformatObject = dataformatFirst as JObject;
                            var subParamObject = pm as JObject;
                            foreach (var item in dataformatObject)
                            {
                                var subConfig = dataformatObject.GetJsonObject(item.Key);
                                var subParam = subParamObject.GetJsonObjectValue(item.Key);
                                subParamObject.SetJsonObjectValue(item.Key, CheckRequest(string.Format("{0}[{2}].{1}", path, item.Key, i), subConfig, subParam, dataset));
                            }
                        }
                        else if (dataformatFirst.Type == JTokenType.Array)
                        {
                            var subDataformat = new JObject() { { "dataformat", dataformatFirst }, { "errormsgs", errormsgs } };
                            paramArray[i] = CheckRequest(string.Format("{0}[{1}]", path, i), subDataformat, pm, dataset);
                        }
                    }
                }
            }
            else if (param.Type == JTokenType.String)
            {
                var value = param.ToString();
                if (min != null && min.Type == JTokenType.Integer)
                {
                    int minLength = min.Value<int>();
                    if (value.Length < minLength)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("less"), ErrorCode.E000000025, path, minLength);
                }
                if (max != null && max.Type == JTokenType.Integer)
                {
                    int maxLength = max.Value<int>();
                    if (value.Length > maxLength)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("excess"), ErrorCode.E000000026, path, maxLength);
                }
                if (range != null && range.Type == JTokenType.Array)
                {
                    Dictionary<string, string> rangeDic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    foreach (var rag in range as JArray)
                    {
                        if (rag != null && rag.Type == JTokenType.String)
                            rangeDic[rag.ToString()] = null;
                    }
                    if (rangeDic.Count > 0 && !rangeDic.ContainsKey(value))
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                }
                else if (range != null && range.Type == JTokenType.Object)
                {
                    Dictionary<string, JToken> rangeDic = new Dictionary<string, JToken>(StringComparer.OrdinalIgnoreCase);
                    foreach (var rag in range as JObject)
                    {
                        rangeDic[rag.Key] = rag.Value;
                    }
                    if (rangeDic.Count > 0)
                    {
                        if (!rangeDic.ContainsKey(value))
                            throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                        param = rangeDic[value];
                    }
                }
            }
            else if (param.Type == JTokenType.Integer)
            {
                var value = param.Value<int>();
                if (min != null && min.Type == JTokenType.Integer)
                {
                    int minValue = min.Value<int>();
                    if (value < minValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("less"), ErrorCode.E000000027, path, minValue);
                }
                if (max != null && max.Type == JTokenType.Integer)
                {
                    int maxValue = max.Value<int>();
                    if (value > maxValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("excess"), ErrorCode.E000000028, path, maxValue);
                }
                if (range != null && range.Type == JTokenType.Array)
                {
                    Dictionary<int, int> rangeDic = new Dictionary<int, int>();
                    foreach (var rag in range as JArray)
                    {
                        if (rag == null)
                            continue;
                        int rangKey = 0;
                        if (rag.Type == JTokenType.Integer)
                            rangKey = rag.Value<int>();
                        else if (rag.Type != JTokenType.String || !int.TryParse(rag.ToString(), out rangKey))
                            continue;
                        rangeDic[rangKey] = 0;
                    }
                    if (rangeDic.Count > 0 && !rangeDic.ContainsKey(value))
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                }
                else if (range != null && range.Type == JTokenType.Object)
                {
                    Dictionary<int, JToken> rangeDic = new Dictionary<int, JToken>();
                    foreach (var rag in range as JObject)
                    {
                        int rangKey = 0;
                        if (!int.TryParse(rag.Key, out rangKey))
                            continue;
                        rangeDic[rangKey] = rag.Value;
                    }
                    if (rangeDic.Count > 0)
                    {
                        if (!rangeDic.ContainsKey(value))
                            throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                        param = rangeDic[value];
                    }
                }
            }
            else if (param.Type == JTokenType.Float)
            {
                var value = param.Value<decimal>();
                if (min != null && (min.Type == JTokenType.Integer || min.Type == JTokenType.Float))
                {
                    decimal minValue = min.Value<decimal>();
                    if (value < minValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("less"), ErrorCode.E000000027, path, minValue);
                }
                if (max != null && (max.Type == JTokenType.Integer || max.Type == JTokenType.Float))
                {
                    decimal maxValue = max.Value<decimal>();
                    if (value > maxValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("excess"), ErrorCode.E000000028, path, maxValue);
                }
                if (range != null && range.Type == JTokenType.Array)
                {
                    Dictionary<decimal, decimal> rangeDic = new Dictionary<decimal, decimal>();
                    foreach (var rag in range as JArray)
                    {
                        if (rag == null)
                            continue;
                        decimal rangKey = 0;
                        if (rag.Type == JTokenType.Integer || rag.Type == JTokenType.Float)
                            rangKey = rag.Value<decimal>();
                        else if (rag.Type != JTokenType.String || !decimal.TryParse(rag.ToString(), out rangKey))
                            continue;
                        rangeDic[rangKey] = 0;
                    }
                    if (rangeDic.Count > 0 && !rangeDic.ContainsKey(value))
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                }
                else if (range != null && range.Type == JTokenType.Object)
                {
                    Dictionary<decimal, JToken> rangeDic = new Dictionary<decimal, JToken>();
                    foreach (var rag in range as JObject)
                    {
                        decimal rangKey = 0;
                        if (!decimal.TryParse(rag.Key, out rangKey))
                            continue;
                        rangeDic[rangKey] = rag.Value;
                    }
                    if (rangeDic.Count > 0)
                    {
                        if (!rangeDic.ContainsKey(value))
                            throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("inrange"), ErrorCode.E000000029, path, string.Join(",", rangeDic.Keys));
                        param = rangeDic[value];
                    }
                }
            }
            else if (param.Type == JTokenType.Date)
            {
                var value = param.Value<DateTime>();
                if (min != null && min.Type == JTokenType.Date)
                {
                    DateTime minValue = min.Value<DateTime>();
                    if (value < minValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("less"), ErrorCode.E000000027, path, minValue);
                }
                if (max != null && max.Type == JTokenType.Date)
                {
                    DateTime maxValue = max.Value<DateTime>();
                    if (value > maxValue)
                        throw ExceptionHelper.GenException(errormsgs.GetJsonObjectStringValue("excess"), ErrorCode.E000000028, path, maxValue);
                }
            }

            TryAddToFilter(path, addToFilter, param, dataset);

            return param;
        }

        /// <summary>
        /// 将指定参数添加到filter
        /// </summary>
        /// <param name="path">参数的路径</param>
        /// <param name="config">配置</param>
        /// <param name="param">参数实体</param>
        /// <param name="dataset">数据集合</param>
        /// <returns></returns>
        private static bool TryAddToFilter(string path, JToken config, JToken param, JObject dataset)
        {
            if (config == null || (config.Type != JTokenType.Boolean && config.Type != JTokenType.Object) || param == null || dataset == null)
                return false;

            if (config.Type == JTokenType.Boolean && !config.Value<bool>())
                return false;

            if (config.Type == JTokenType.Object && !config.HasValues)
                return false;
            
            var request = dataset.GetJsonObject("request");
            if (request == null)
                return false;
            string suffix = null;
            if (!string.IsNullOrWhiteSpace(path))
            {
                var paths = path.Split('.');
                suffix = paths[paths.Length - 1];
            }
            var configObject = config.Type == JTokenType.Object ? Utils.FilterConvert(Utils.ParseObject(config, dataset) as JObject) : null;
            if (configObject == null)
            {
                if (string.IsNullOrWhiteSpace(suffix))
                    return false;
                configObject = Utils.FilterConvert(new JObject() { { suffix, param } });
            }
            else
            {
                var field = configObject.GetJsonObjectStringValue("field");
                if (string.IsNullOrWhiteSpace(field))
                {
                    if (string.IsNullOrWhiteSpace(suffix))
                        return false;
                    field = suffix;
                    configObject.SetJsonObjectValue("field", field);
                }
                if ((configObject.GetJsonObjectValue("groups") as JArray) == null && configObject.GetJsonObjectValue("value") == null)
                {
                    if (param is JValue)
                        configObject.SetJsonObjectValue("value", param);
                    else
                        configObject = Utils.FilterConvert(new JObject() { { field, param } });
                }
            }
            if (configObject == null)
                return false;

            var filter = request.GetJsonObjectValue("filter") as JObject;
            if (filter == null)
            {
                filter = new JObject() { { "groups", new JArray() } };
                request.SetJsonObjectValue("filter", filter);
            }
            else if (!filter.ContainsProperty("groups"))
            {
                filter = new JObject() { { "groups", new JArray() { filter } } };
            }
            (filter.GetJsonObjectValue("groups") as JArray)?.Add(configObject);

            return true;
        }

        /// <summary>
        /// 检查Process的ID是否合规
        /// </summary>
        /// <param name="id"></param>
        /// <param name="dataset"></param>
        /// <param name="processPath"></param>
        private static void CheckProcessId(string id, JObject dataset, string processPath)
        {
            if (!string.IsNullOrWhiteSpace(id))
            {
                if (Utils.DataIdKeywords.Contains(id.ToLower()))
                    throw new ConfigException("{0}.id can not be one of {1}", processPath, string.Join("/", Utils.DataIdKeywords));

                if (dataset != null && dataset.ContainsProperty(id))
                    throw new ConfigException("{0}.id is duplicate", processPath);
            }
        }

        /// <summary>
        /// 解析Linq
        /// </summary>
        /// <param name="linq">linq</param>
        /// <returns>解析后的Url</returns>
        private static LinqCommand ParseLinqString(string linq, JObject dataset)
        {
            if (string.IsNullOrWhiteSpace(linq))
                return null;
            List<LinqParameter> parameters = new List<LinqParameter>();
            var indexAdjustment = 0;
            var parameterNames = new Dictionary<string, string>();
            var matches = Regex.Matches(linq, Utils.ParamRegexPattern);
            foreach (Match match in matches)
            {
                Capture capture = match.Groups[0];
                int index = capture.Index + indexAdjustment;
                int length = capture.Length;
                string name = capture.Value;
                string paramname = Utils.GetSha1Hash(name);
                if (parameterNames.ContainsKey(paramname))
                {
                    linq = string.Format("{0}{1}{2}", linq.Substring(0, index), parameterNames[paramname], linq.Substring(index + length));
                    indexAdjustment += parameterNames[paramname].Length - name.Length;
                    continue;
                }

                List<KeyValuePair<string, string>> functions = null;
                string path = match.Groups[1].Value;
                string[] paths = path.Split('.');
                string suffix = paths[paths.Length - 1];
                if (!string.IsNullOrWhiteSpace(match.Groups[2].Value))
                {
                    functions = new List<KeyValuePair<string, string>>();
                    functions.Add(new KeyValuePair<string, string>(suffix, match.Groups[3].Value));
                    for (int i = 0; i < match.Groups[4].Captures.Count; i++)
                        functions.Add(new KeyValuePair<string, string>(match.Groups[4].Captures[i].Value, match.Groups[5].Captures[i].Value));
                    path = path.Substring(0, path.Length - suffix.Length).TrimEnd('.');
                }
                
                parameters.Add(new LinqParameter() { ParameterName = paramname, ParameterPath = path, Functions = functions });
                string valueString = string.Format("dataset[\"{0}\"]", paramname);
                linq = string.Format("{0}{1}{2}", linq.Substring(0, index), valueString, linq.Substring(index + length));
                parameterNames.Add(paramname, valueString);
                indexAdjustment += valueString.Length - name.Length;
            }

            return new LinqCommand() { Parameters = parameters, Linq = linq };
        }

        /// <summary>
        /// 解析条件
        /// </summary>
        /// <param name="config">条件等式</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>true/false</returns>
        private static JToken ParseCondition(JToken config, JObject dataset)
        {
            string condition = null;
            if (config == null || config.Type != JTokenType.Boolean && config.Type != JTokenType.Integer && config.Type != JTokenType.String)
                return false;
            else if (config.Type == JTokenType.Boolean)
                return config;
            else if (config.Type == JTokenType.Integer)
                return config.Value<int>() > 0;
            else if (config.Type == JTokenType.String)
            {
                condition = config.ToString().Trim(' ');
                string prefix = (condition.Length >= 6 ? condition.Substring(0, 6) : condition).ToLower();
                if (prefix.StartsWith("js:"))
                    condition = condition.Substring(3);
                else if (prefix.StartsWith("sql:"))
                    return condition.Substring(4);
                else if (prefix.StartsWith("select"))
                    return condition;
            }

            if (string.IsNullOrWhiteSpace(condition))
                return false;
            var indexAdjustment = 0;
            var parameterNames = new Dictionary<string, string>();
            var matches = Regex.Matches(condition, Utils.ParamRegexPattern);
            foreach (Match match in matches)
            {
                Capture capture = match.Groups[0];
                int index = capture.Index + indexAdjustment;
                int length = capture.Length;
                string name = capture.Value;
                string paramname = Utils.GetSha1Hash(name);
                if (parameterNames.ContainsKey(paramname))
                {
                    condition = string.Format("{0}{1}{2}", condition.Substring(0, index), parameterNames[paramname], condition.Substring(index + length));
                    indexAdjustment += parameterNames[paramname].Length - name.Length;
                    continue;
                }

                List<KeyValuePair<string, string>> functions = null;
                string path = match.Groups[1].Value;
                string[] paths = path.Split('.');
                string suffix = paths[paths.Length - 1];
                if (!string.IsNullOrWhiteSpace(match.Groups[2].Value))
                {
                    functions = new List<KeyValuePair<string, string>>();
                    functions.Add(new KeyValuePair<string, string>(suffix, match.Groups[3].Value));
                    for (int i = 0; i < match.Groups[4].Captures.Count; i++)
                        functions.Add(new KeyValuePair<string, string>(match.Groups[4].Captures[i].Value, match.Groups[5].Captures[i].Value));
                    path = path.Substring(0, path.Length - suffix.Length).TrimEnd('.');
                }

                string valueString = "(undefined)";
                var value = Utils.GetParameter(path, dataset, functions).Evaluate();
                if (value != null)
                {
                    if (value.Type == JTokenType.String || value.Type == JTokenType.Guid)
                    {
                        valueString = string.Format("{0}", value.ToString(Formatting.None));
                    }
                    else if (value.Type == JTokenType.Date)
                    {
                        DateTime date = value.Value<DateTime>();
                        valueString = string.Format("new Date({0},{1},{2},{3},{4},{5},{6})", date.Year, date.Month - 1, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond);
                    }
                    else if (value.Type == JTokenType.Integer || value.Type == JTokenType.Boolean || value.Type == JTokenType.Float)
                    {
                        valueString = value.ToString().ToLower();
                    }
                    else if (value.Type == JTokenType.Object || value.Type == JTokenType.Array)
                    {
                        valueString = string.Format("({0})", value.ToString(Formatting.None));
                    }
                    else if (value.Type == JTokenType.Null)
                    {
                        valueString = "(null)";
                    }
                }

                condition = string.Format("{0}{1}{2}", condition.Substring(0, index), valueString, condition.Substring(index + length));
                parameterNames.Add(paramname, valueString);
                indexAdjustment += valueString.Length - name.Length;
            }

            if (string.IsNullOrWhiteSpace(condition))
                return false;
            if (condition.Equals("true", StringComparison.OrdinalIgnoreCase) || condition == "1")
                return true;
            else if (condition.Equals("false", StringComparison.OrdinalIgnoreCase) || condition == "0")
                return false;
            else
            {
                try
                {
                    using (var engine = new ScriptEngine())
                    {
                        string jsContent = @"
                            var condition = function(){
                                return " + condition + @";
                            }
                        ";
                        engine.Execute(jsContent);

                        var result = engine.Script.condition();
                        return result;
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.Warn("Parse Condition Error", ex);
                    return false;
                }
            }
        }

        /// <summary>
        /// 解析Url
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="dataset">数据集合</param>
        /// <param name="isQueryString">是否是参数部分（问号后面的部分）</param>
        /// <param name="keepEmptyValue">是否保留空参数</param>
        /// <returns>解析后的Url</returns>
        private static string ParseUrlString(string url, JObject dataset, bool isQueryString = false, bool keepEmptyValue = true)
        {
            if (string.IsNullOrWhiteSpace(url))
                return url;
            var indexAdjustment = 0;
            var parameterNames = new Dictionary<string, string>();
            var matches = Regex.Matches(url, Utils.ParamRegexPattern);
            foreach (Match match in matches)
            {
                Capture capture = match.Groups[0];
                int index = capture.Index + indexAdjustment;
                int length = capture.Length;
                string name = capture.Value;
                string paramname = Utils.GetSha1Hash(name);
                if (parameterNames.ContainsKey(paramname))
                {
                    url = string.Format("{0}{1}{2}", url.Substring(0, index), parameterNames[paramname], url.Substring(index + length));
                    indexAdjustment += parameterNames[paramname].Length - name.Length;
                    continue;
                }

                List<KeyValuePair<string, string>> functions = null;
                string path = match.Groups[1].Value;
                string[] paths = path.Split('.');
                string suffix = paths[paths.Length - 1];
                if (!string.IsNullOrWhiteSpace(match.Groups[2].Value))
                {
                    functions = new List<KeyValuePair<string, string>>();
                    functions.Add(new KeyValuePair<string, string>(suffix, match.Groups[3].Value));
                    for (int i = 0; i < match.Groups[4].Captures.Count; i++)
                        functions.Add(new KeyValuePair<string, string>(match.Groups[4].Captures[i].Value, match.Groups[5].Captures[i].Value));
                    path = path.Substring(0, path.Length - suffix.Length).TrimEnd('.');
                }

                string valueString = string.Empty;
                var value = Utils.GetParameter(path, dataset, functions).Evaluate();
                if (value != null && value is JValue)
                {
                    valueString = isQueryString ? HttpUtility.UrlEncode(value.ToString()) : value.ToString();
                }
                else if (value != null && value.Type == JTokenType.Array && value.HasValues && value.Any(v => v is JValue))
                {
                    List<string> vals = new List<string>();
                    foreach (var val in value)
                    {
                        if (val is JValue)
                        {
                            vals.Add(isQueryString ? HttpUtility.UrlEncode(value.ToString()) : value.ToString());
                        }
                    }
                    valueString = string.Join(",", vals);
                }
                else if (value != null)
                {
                    valueString = HttpUtility.UrlEncode(value.ToString(Formatting.None));
                }

                url = string.Format("{0}{1}{2}", url.Substring(0, index), valueString, url.Substring(index + length));
                parameterNames.Add(paramname, valueString);
                indexAdjustment += valueString.Length - name.Length;
            }

            if (!keepEmptyValue)
            {
                string queryString = null;
                if (url.IndexOf("?") > -1)
                {
                    var urls = url.Split('?');
                    queryString = urls[1];
                    url = urls[0] + "?";
                }
                else if (isQueryString)
                {
                    queryString = url;
                    url = string.Empty;
                }
                if (!string.IsNullOrWhiteSpace(queryString))
                {
                    List<string> paramList = new List<string>();
                    foreach (var item in queryString.Split('&'))
                    {
                        var keyValue = item.Split('=');
                        if (keyValue.Length == 2 && !string.IsNullOrWhiteSpace(keyValue[1]))
                            paramList.Add(item);
                    }
                    url += string.Join("&", paramList);
                }
            }

            return url;
        }

        /// <summary>
        /// 解析SQL
        /// </summary>
        /// <param name="dbBase">数据库实体</param>
        /// <param name="sql">SQL</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>SQL命令实体</returns>
        private static DBCommand ParseSqlString(DBBase dbBase, string sql, JObject dataset)
        {
            if (string.IsNullOrWhiteSpace(sql))
                return null;
            var tempTables = new List<string>();
            var sqlNativeParameters = new List<string>();
            var parameters = new List<DBParameter>();
            var indexAdjustment = 0;
            var parameterNames = new Dictionary<string, string>();
            var matches = Regex.Matches(sql, Utils.ParamRegexPattern);
            foreach (Match match in matches)
            {
                Capture capture = match.Groups[0];
                int index = capture.Index + indexAdjustment;
                int length = capture.Length;
                string name = capture.Value;
                string path = match.Groups[1].Value;
                if (!dataset.ContainsProperty(path) && !name.Contains(".") && !name.EndsWith("]"))
                {
                    if (!name.EndsWith(")")) //为了避免原生的sql变量被替换
                    {
                        sqlNativeParameters.Add(name.ToLower());
                        continue;
                    }
                    else if (sqlNativeParameters.Contains(name.Split('(')[0].ToLower())) //避免表变量数据插入语句被替换。例如：insert into @VariableTable(col1,col2) values (1,2)
                    {
                        continue;
                    }
                }
                string paramname = Utils.GetSha1Hash(name);
                if (parameterNames.ContainsKey(paramname))
                {
                    sql = string.Format("{0}{1}{2}", sql.Substring(0, index), parameterNames[paramname], sql.Substring(index + length));
                    indexAdjustment += parameterNames[paramname].Length - name.Length;
                    continue;
                }

                List<KeyValuePair<string, string>> functions = null;
                string[] paths = path.Split('.');
                string suffix = paths[paths.Length - 1];
                if (!string.IsNullOrWhiteSpace(match.Groups[2].Value))
                {
                    functions = new List<KeyValuePair<string, string>>();
                    functions.Add(new KeyValuePair<string, string>(suffix, match.Groups[3].Value));
                    for (int i = 0; i < match.Groups[4].Captures.Count; i++)
                        functions.Add(new KeyValuePair<string, string>(match.Groups[4].Captures[i].Value, match.Groups[5].Captures[i].Value));
                    path = path.Substring(0, path.Length - suffix.Length).TrimEnd('.');
                    suffix = paths.Length > 1 ? paths[paths.Length - 2] : string.Empty;
                }

                JToken value = Utils.GetParameter(path, dataset, functions).Evaluate();

                if (path == "request.filter" || path == "query.filter")
                {
                    #region filter
                    var operators = dataset.GetJsonObject("constant").GetJsonObject("filterOperator") ?? new JObject();
                    foreach (var op in dbBase.Operators)
                    {
                        if (!operators.ContainsProperty(op.Key))
                            operators.SetJsonObjectValue(op.Key, op.Value);
                    }
                    string filterString = "1=1";
                    var filterCmd = ParseFilter(dbBase, operators, value, paramname);
                    if (filterCmd != null)
                    {
                        filterString = filterCmd.Sql;
                        parameters.AddRange(filterCmd.Parameters);
                    }
                    sql = string.Format("{0}{1}{2}", sql.Substring(0, index), filterString, sql.Substring(index + length));
                    parameterNames.Add(paramname, filterString);
                    indexAdjustment += filterString.Length - name.Length;
                    #endregion
                }
                else if (path == "request.sort" || path == "query.sort")
                {
                    #region sort
                    StringBuilder sortBuilder = new StringBuilder();
                    if (value != null && value.Type == JTokenType.Array)
                    {
                        foreach (JToken item in value as JArray)
                        {
                            JObject sortItem = item as JObject;
                            if (sortItem == null)
                                continue;

                            string field = sortItem.GetJsonObjectStringValue("field");
                            string mode = sortItem.GetJsonObjectStringValue("mode")?.ToLower();

                            if (string.IsNullOrWhiteSpace(field))
                                continue;

                            string fieldPrefix = string.Empty;
                            int dotIndex = field.IndexOf('.');
                            if (dotIndex > 0)
                            {
                                fieldPrefix = field.Substring(0, dotIndex + 1);
                                field = field.Substring(dotIndex + 1);
                            }

                            field = fieldPrefix + string.Format(dbBase.EscapeFormat, field);

                            if (mode != "asc" && mode != "desc")
                                mode = "asc";

                            if (sortBuilder.Length > 0)
                                sortBuilder.Append(",");
                            sortBuilder.AppendFormat("{0} {1}", field, mode);
                        }
                    }
                    //if (sortBuilder.Length == 0)
                    //    sortBuilder.Append("1");
                    string sortString = sortBuilder.ToString();
                    sql = string.Format("{0}{1}{2}", sql.Substring(0, index), sortString, sql.Substring(index + length));
                    parameterNames.Add(paramname, sortString);
                    indexAdjustment += sortString.Length - name.Length;
                    #endregion
                }
                else
                {
                    var parameter = GetSqlParameter(paramname, suffix, value);
                    if (parameter != null && parameter.Count > 0)
                    {
                        if (value?.Type == JTokenType.Array || value?.Type == JTokenType.Object)
                        {
                            decimal maxFields = 2000;
                            if (parameter[0].Values.Count * parameter.Count > maxFields) //参数太多，用临时表
                            {
                                int rowCount = (int)(maxFields / parameter[0].Values.Count);
                                if (rowCount == 0)
                                    throw ExceptionHelper.GenException(ErrorCode.E000000006, name);
                                int processIndex = 0;
                                List<string> rows = null;
                                List<DBParameter> rowParameters = null;
                                foreach (var param in parameter)
                                {
                                    if (processIndex % rowCount == 0)
                                    {
                                        rows = new List<string>();
                                        rowParameters = new List<DBParameter>();
                                    }
                                    processIndex++;
                                    rowParameters.AddRange(param.Values);
                                    List<string> cols = new List<string>();
                                    foreach (var pa in param.OrderBy(p => p.Key))
                                        cols.Add(string.Format("{0} as {1}", pa.Value.ParameterName, string.Format(dbBase.EscapeFormat, pa.Key)));
                                    rows.Add(string.Join(",", cols));
                                    if (processIndex % rowCount == 0 || processIndex == parameter.Count)
                                    {
                                        int tempTableId = (int)Math.Ceiling(processIndex * 1m / rowCount);
                                        string join = string.Format("select {0}", string.Join(" union all select ", rows));
                                        var ds = dbBase.Query(join, rowParameters);
                                        var datajson = JSONHelper.DataTableToJArray(ds.Tables?[0]);
                                        if (datajson != null)
                                        {
                                            var tempTableCols = new List<string>();
                                            var tempTableColTypes = new Dictionary<string, string>();
                                            var maxSizeParameters = new Dictionary<string, JValue>();
                                            foreach (JObject data in datajson)
                                            {
                                                foreach (var item in data)
                                                {

                                                    if (!maxSizeParameters.ContainsKey(item.Key))
                                                        maxSizeParameters.Add(item.Key, item.Value as JValue);
                                                    else
                                                    {
                                                        if (maxSizeParameters[item.Key].Type == JTokenType.Null)
                                                        {
                                                            maxSizeParameters[item.Key] = item.Value as JValue;
                                                        }
                                                        else if (item.Value.Type == JTokenType.String && maxSizeParameters[item.Key].ToString().Length < item.Value.ToString().Length)
                                                        {
                                                            maxSizeParameters[item.Key] = item.Value as JValue;
                                                        }
                                                    }
                                                    switch (maxSizeParameters[item.Key].Type)
                                                    {
                                                        case JTokenType.Integer:
                                                            tempTableColTypes[item.Key] = "bigint";
                                                            break;
                                                        case JTokenType.Float:
                                                            tempTableColTypes[item.Key] = "decimal(38,10)";
                                                            break;
                                                        case JTokenType.Boolean:
                                                            tempTableColTypes[item.Key] = "bit";
                                                            break;
                                                        case JTokenType.Date:
                                                            tempTableColTypes[item.Key] = "datetime";
                                                            break;
                                                        default:
                                                            tempTableColTypes[item.Key] = "nvarchar({0})";
                                                            break;
                                                    }
                                                }
                                            }
                                            foreach (var item in maxSizeParameters)
                                            {
                                                string type = tempTableColTypes[item.Key];
                                                if (item.Value.Type == JTokenType.String || item.Value.Type == JTokenType.Null)
                                                {
                                                    var valLen = item.Value?.ToString()?.Length ?? 0;
                                                    type = string.Format(type, valLen > 0 ? valLen : 1);
                                                }
                                                string collate = dbBase.DatabaseType == DatabaseType.MSSQL && type.StartsWith("nvarchar")  ? " COLLATE DATABASE_DEFAULT" : "";
                                                tempTableCols.Add(string.Format("{0} {1}{2} null", string.Format(dbBase.EscapeFormat, item.Key), type, collate));
                                            }
                                            string tempTableName = null;
                                            string tempTableCreateSql = null;
                                            if (dbBase.DatabaseType == DatabaseType.MSSQL)
                                            {
                                                tempTableName = string.Format("#{0}_{1}", Guid.NewGuid().ToString("N"), tempTableId);
                                                tempTableCreateSql = "create table {0}({1})";
                                            }
                                            else if (dbBase.DatabaseType == DatabaseType.MYSQL)
                                            {
                                                tempTableName = string.Format("{0}_{1}", Guid.NewGuid().ToString("N"), tempTableId);
                                                tempTableCreateSql = "create temporary table {0}({1})";
                                            }
                                            dbBase.ExecuteNonQuery(string.Format(tempTableCreateSql, tempTableName, string.Join(",", tempTableCols)));
                                            dbBase.ExecuteNonQuery(string.Format("insert into {0} {1}", tempTableName, join), rowParameters);
                                            tempTables.Add(tempTableName);
                                        }
                                    }
                                }
                                string parameterSql = string.Format("select * from {0}", string.Join(" union all select * from ", tempTables));
                                sql = string.Format("{0}{1}{2}", sql.Substring(0, index), parameterSql, sql.Substring(index + length));
                                parameterNames.Add(paramname, parameterSql);
                                indexAdjustment += parameterSql.Length - name.Length;
                            }
                            else
                            {
                                List<string> rows = new List<string>();
                                foreach (var param in parameter)
                                {
                                    parameters.AddRange(param.Values);
                                    List<string> cols = new List<string>();
                                    foreach (var pa in param.OrderBy(p => p.Key))
                                        cols.Add(string.Format("{0} as {1}", pa.Value.ParameterName, string.Format(dbBase.EscapeFormat, pa.Key)));
                                    rows.Add(string.Join(",", cols));
                                }
                                string parameterSql = string.Format("select {0}", string.Join(" union all select ", rows));
                                sql = string.Format("{0}{1}{2}", sql.Substring(0, index), parameterSql, sql.Substring(index + length));
                                parameterNames.Add(paramname, parameterSql);
                                indexAdjustment += parameterSql.Length - name.Length;
                            }
                        }
                        else
                        {
                            var param = parameter[0].FirstOrDefault();
                            parameters.Add(param.Value);
                            sql = string.Format("{0}{1}{2}", sql.Substring(0, index), param.Value.ParameterName, sql.Substring(index + length));
                            parameterNames.Add(paramname, param.Value.ParameterName);
                            indexAdjustment += param.Value.ParameterName.Length - name.Length;
                        }
                    }
                }
            }

            if (tempTables.Count > 0)
                sql += string.Format(";drop table {0}", string.Join(";drop table ", tempTables));

            return new DBCommand() { Parameters = parameters, Sql = sql };
        }

        /// <summary>
        /// 解析过滤器
        /// </summary>
        /// <param name="dbBase">数据库实体</param>
        /// <param name="operators">比较运算符逻辑集合</param>
        /// <param name="filter">过滤器结构体</param>
        /// <param name="paramName">数据库参数名称</param>
        /// <param name="parentField">父级字段名</param>
        /// <returns>过滤器解析结果</returns>
        private static DBCommand ParseFilter(DBBase dbBase, JObject operators, JToken filter, string paramName, string parentField = null)
        {
            if (string.IsNullOrWhiteSpace(paramName) || filter == null)
                return null;
            JObject filterObject = Utils.FilterConvert(filter);
            if (filterObject == null)
                return null;
            if (!paramName.StartsWith("@"))
                paramName = "@" + paramName;
            string field = filterObject.GetJsonObjectStringValue("field");
            if (!string.IsNullOrWhiteSpace(field))
            {
                char escapeStart = dbBase.EscapeFormat.First();
                char escapeEnd = dbBase.EscapeFormat.Last();
                string fieldPrefix = string.Empty;
                int dotIndex = field.IndexOf('.');
                if (dotIndex > 0)
                {
                    fieldPrefix = field.Substring(0, dotIndex + 1);
                    field = field.Substring(dotIndex + 1);
                }
                if (field.First() == escapeStart && field.Last() == escapeEnd)
                {
                    string rawField = fieldPrefix + field.TrimStart(escapeStart).TrimEnd(escapeEnd);
                    field = rawField.Contains(escapeStart) || rawField.Contains(escapeEnd) ? null : fieldPrefix + field;
                }
                else
                {
                    string rawField = fieldPrefix + field;
                    field = rawField.Contains(escapeStart) || rawField.Contains(escapeEnd) ? null : fieldPrefix + string.Format(dbBase.EscapeFormat, field);
                }
            }
            else
            {
                field = parentField;
            }

            JToken groups = filterObject.GetJsonObjectValue("groups") as JArray;
            if (groups != null && groups.HasValues)
            {
                int i = 1;
                var parameters = new List<DBParameter>();
                var groupList = new List<string>();
                foreach (var group in groups)
                {
                    DBCommand cmd = ParseFilter(dbBase, operators, group, paramName + "_" + i, field);
                    if (cmd != null)
                    {
                        parameters.AddRange(cmd.Parameters);
                        groupList.Add(cmd.Sql);
                        i++;
                    }
                }
                if (groupList.Count > 0)
                {
                    if (groupList.Count == 1)
                        return new DBCommand() { Parameters = parameters, Sql = groupList[0] };
                    else
                    {
                        string mode = filterObject.GetJsonObjectStringValue("mode")?.ToLower();
                        if (mode != "and" && mode != "or" && mode != "in")
                            mode = "and";
                        if (mode == "in" && (string.IsNullOrWhiteSpace(field) || parameters.Count != groupList.Count))
                            mode = "or";
                        if (mode == "in")
                            return new DBCommand() { Parameters = parameters, Sql = string.Format("{0} in ({1})", field, string.Join(",", parameters.Select(p => p.ParameterName).ToArray())) };
                        else
                            return new DBCommand() { Parameters = parameters, Sql = string.Format("({0})", string.Join(" " + mode + " ", groupList.ToArray())) };
                    }
                }
            }
            else if (!string.IsNullOrWhiteSpace(field))
            {
                var value = filterObject.GetJsonObjectValue("value");
                var values = filterObject.GetJsonObjectValue("values") as JArray;
                var operatorStr = filterObject.GetJsonObjectStringValue("operator");
                var nullable = filterObject.GetJsonObjectBooleanValue("nullable");
                if (string.IsNullOrWhiteSpace(operatorStr))
                    operatorStr = "equals";
                string operatorFormat = operators.GetJsonObjectStringValue(operatorStr);
                if (!string.IsNullOrWhiteSpace(operatorFormat) && value != null && value is JValue && (value.Type != JTokenType.Null || nullable))
                {
                    var parameters = new List<DBParameter>() { new DBParameter(paramName, value) };
                    var args = new List<object> { field, paramName };
                    if (values != null && values.Count > 0)
                    {
                        int i = 1;
                        foreach (var v in values)
                        {
                            var parameter = new DBParameter(paramName + "_" + i, v);
                            parameters.Add(parameter);
                            args.Add(parameter.ParameterName);
                            i++;
                        }
                    }
                    return new DBCommand() { Parameters = parameters, Sql = string.Format(operatorFormat, args.ToArray()) };
                }
            }

            return null;
        }

        /// <summary>
        /// 生成SQL参数
        /// </summary>
        /// <param name="paramName">参数名称</param>
        /// <param name="columnName">数据表列名</param>
        /// <param name="value">参数值</param>
        /// <returns>SQL参数集合</returns>
        private static List<Dictionary<string, DBParameter>> GetSqlParameter(string paramName, string columnName, JToken value)
        {
            List<Dictionary<string, DBParameter>> parameters = new List<Dictionary<string, DBParameter>>();

            if (string.IsNullOrWhiteSpace(paramName))
                return parameters;

            if (string.IsNullOrWhiteSpace(columnName))
                columnName = paramName;

            paramName = "@" + paramName;
            columnName = columnName.Split('[')[0]; //有中括号结尾的，去掉中括号
            if (value != null && value is JValue)
            {
                var parameter = new DBParameter(paramName, value);
                parameters.Add(new Dictionary<string, DBParameter>() { { columnName, parameter } });
            }
            else if (value != null && value.Type == JTokenType.Array && value.HasValues && value.Any(v => v is JValue))
            {
                int index = 0;
                foreach (var val in value)
                {
                    index++;
                    var parameter = new DBParameter(paramName + "_" + index, val);
                    parameters.Add(new Dictionary<string, DBParameter>() { { columnName, parameter } });
                }
            }
            else if (value != null && value.Type == JTokenType.Array && value.HasValues && value.Any(v => v is JObject))
            {
                int index = 0;
                List<string> allCols = new List<string>();
                foreach (var v in value)
                {
                    index++;
                    if (v?.Type != JTokenType.Object)
                        continue;
                    var rows = new Dictionary<string, DBParameter>();
                    foreach (var col in v as JObject)
                    {
                        JToken val = col.Value;
                        string key = col.Key.ToLower();
                        if (!allCols.Contains(key))
                            allCols.Add(key);
                        var parameter = new DBParameter(paramName + key + "_" + index, val);
                        rows.Add(key, parameter);
                    }
                    parameters.Add(rows);
                }
                index = 0;
                foreach (var param in parameters)
                {
                    index++;
                    foreach (var col in allCols)
                    {
                        if (!param.ContainsKey(col)) //由于要生成一张表，缺失的字段补上
                        {
                            var parameter = new DBParameter(paramName + col + "_" + index, null);
                            param.Add(col, parameter);
                        }
                    }
                }
            }
            else if (value != null && value.Type == JTokenType.Object && value.HasValues)
            {
                var rows = new Dictionary<string, DBParameter>();
                foreach (var col in value as JObject)
                {
                    JToken val = col.Value;
                    string key = col.Key.ToLower();
                    var parameter = new DBParameter(paramName + key, val);
                    rows.Add(key, parameter);
                }
                parameters.Add(rows);
            }
            else
            {
                var parameter = new DBParameter(paramName, null);
                parameters.Add(new Dictionary<string, DBParameter>() { { columnName, parameter } });
            }
            return parameters;
        }

        /// <summary>
        /// 通过JSON实体转换为SQL
        /// </summary>
        /// <param name="config">JSON实体</param>
        /// <param name="dataset">数据集合</param>
        /// <param name="dbBase">数据库实体</param>
        /// <param name="cmdType">SQL类型（Insert/Update/Delete）</param>
        /// <param name="table">数据库表名称</param>
        /// <returns>SQL命令实体集合</returns>
        private static List<DBCommand> GetSqlCommands(JToken config, JObject dataset, DBBase dbBase, string cmdType, string table = null)
        {
            List<DBCommand> cmds = new List<DBCommand>();
            if (config == null || string.IsNullOrWhiteSpace(cmdType) || (config.Type != JTokenType.Object && config.Type != JTokenType.Array))
                return cmds;

            cmdType = cmdType.ToLower();
            var runtimedata = RuntimeData.GetData(dataset, string.IsNullOrWhiteSpace(table) ? config : null);
            var cursor = runtimedata.Cursor;
            int index = 0;
            
            JArray array = new JArray();
            if (config.Type == JTokenType.Object)
                array.Add(config);
            else if (config.Type == JTokenType.Array)
                array = config as JArray;

            foreach (var item in array)
            {
                index++;
                if (item.Type != JTokenType.Object)
                    continue;

                JObject controls = new JObject();
                List<DBParameter> parameters = new List<DBParameter>();
                List<DBParameter> conditionParameters = new List<DBParameter>();
                List<DBCommand> subCommands = new List<DBCommand>();
                string identityField = null;
                List<string> cols = new List<string>();
                List<string> values = new List<string>();
                List<string> keyvalues = new List<string>();
                foreach (var field in item as JObject)
                {
                    if (field.Key.StartsWith("!")) //字段名称带!开头的表示非数据库表/非数据表字段
                        continue;
                    string fieldKey = field.Key.ToLower();
                    if (config.Type == JTokenType.Object)
                        runtimedata.SetCursor(string.Format("{0}.{1}", cursor, fieldKey));
                    else if (config.Type == JTokenType.Array)
                        runtimedata.SetCursor(string.Format("{0}[{1}].{2}", cursor, index - 1, fieldKey));
                    if ((field.Value.Type == JTokenType.Object || field.Value.Type == JTokenType.Array) && !Utils.SqlKeywords.Contains(fieldKey))
                    {
                        var subCmds = GetSqlCommands(field.Value, dataset, dbBase, cmdType, field.Key);
                        if (subCmds != null && subCmds.Count > 0)
                            subCommands.AddRange(subCmds);
                    }
                    else if (!string.IsNullOrWhiteSpace(table))
                    {
                        if (Utils.SqlKeywords.Contains(fieldKey))
                        {
                            if (fieldKey == "if")
                            {
                                var condition = ParseCondition(field.Value, dataset);
                                if (condition.Type == JTokenType.String) //是SQL脚本
                                {
                                    var dbcommand = ParseSqlString(dbBase, condition.ToString(), dataset);
                                    if (dbcommand == null)
                                        condition = false;
                                    else
                                    {
                                        condition = dbcommand.Sql;
                                        if (dbcommand.Parameters != null && dbcommand.Parameters.Count > 0)
                                            conditionParameters.AddRange(dbcommand.Parameters);
                                    }
                                }
                                controls.Add(fieldKey, condition);
                            }
                            else if (fieldKey == "identity")
                            {
                                if (cmdType == "insert" || cmdType == "any")
                                {
                                    identityField = field.Value.Type == JTokenType.String ? field.Value.ToString() : null;
                                }
                            }
                            else
                            {
                                string controlcmd = field.Value.Type == JTokenType.String ? field.Value.ToString() : string.Empty;
                                var dbcommand = ParseSqlString(dbBase, controlcmd, dataset);
                                if (dbcommand != null)
                                {
                                    controlcmd = dbcommand.Sql;
                                    if (dbcommand.Parameters != null && dbcommand.Parameters.Count > 0)
                                        parameters.AddRange(dbcommand.Parameters);
                                }
                                controls.Add(fieldKey, controlcmd);
                            }
                        }
                        else 
                        {
                            string col = string.Format(dbBase.EscapeFormat, field.Key);
                            string val = string.Format("@{0}", field.Key);
                            cols.Add(col);
                            values.Add(val);
                            keyvalues.Add(string.Format("{0}={1}", col, val));
                            var parameter = GetSqlParameter(field.Key, null, field.Value);
                            if (parameter != null && parameter.Count > 0)
                                foreach (var param in parameter)
                                    parameters.AddRange(param.Values);
                        }
                    }
                }
                if (!string.IsNullOrWhiteSpace(table))
                {
                    DBCommand cmd = null;
                    string sql = controls.GetJsonObjectStringValue("sql");
                    string from = controls.GetJsonObjectStringValue("from");
                    string where = controls.GetJsonObjectStringValue("where");
                    switch (cmdType)
                    {
                        case "insert":
                            if (cols.Count > 0 && values.Count > 0)
                            {
                                cmd = new DBAddCommand() { Identity = identityField, SubCommands = subCommands };
                                cmd.Sql = string.Format("insert into {0}({1}) values ({2}){3}", table, string.Join(",", cols), string.Join(",", values), !string.IsNullOrWhiteSpace(identityField) ? ";select scope_identity()" : "");
                                cmd.Parameters = parameters;
                            }
                            break;
                        case "update":
                            if (keyvalues.Count > 0 && !string.IsNullOrWhiteSpace(where))
                            {
                                if (!string.IsNullOrWhiteSpace(from))
                                    from = string.Format(" from {0}", from);
                                cmd = new DBActionCommand() { SubCommands = subCommands };
                                cmd.Sql = string.Format("update {0} set {1}{2} where {3}", table, string.Join(",", keyvalues), from, where);
                                cmd.Parameters = parameters;
                            }
                            break;
                        case "delete":
                            if (!string.IsNullOrWhiteSpace(where))
                            {
                                if (!string.IsNullOrWhiteSpace(from))
                                    from = string.Format(" from {0}", from);
                                cmd = new DBActionCommand() { SubCommands = subCommands };
                                cmd.Sql = string.Format("delete {0}{1} where {2}", (string.IsNullOrWhiteSpace(from) ? "from " : "") + table, from, where);
                                cmd.Parameters = parameters;
                            }
                            break;
                        case "any":
                            if (!string.IsNullOrWhiteSpace(sql))
                            {
                                if (!string.IsNullOrWhiteSpace(identityField))
                                    cmd = new DBAddCommand() { Identity = identityField, SubCommands = subCommands };
                                else
                                    cmd = new DBActionCommand() { SubCommands = subCommands };
                                cmd.Sql = sql;
                                cmd.Parameters = parameters;
                            }
                            break;
                    }
                    if (cmd != null)
                    {
                        var condition = controls.GetJsonObjectValue("if");
                        if (!controls.ContainsProperty("if"))
                            condition = true;
                        else if (condition == null || condition.Type != JTokenType.Boolean && condition.Type != JTokenType.String)
                            condition = false;

                        if (condition.Type == JTokenType.Boolean && condition.Value<bool>())
                        {
                            cmds.Add(cmd);
                        }
                        else if (condition.Type == JTokenType.String)
                        {
                            var conditionCmd = new DBConditionCommand();
                            conditionCmd.Sql = condition.ToString();
                            conditionCmd.Parameters = conditionParameters;
                            conditionCmd.Command = cmd;
                            cmds.Add(conditionCmd);
                        }
                    }
                }
                else
                {
                    cmds.AddRange(subCommands);
                }
            }

            runtimedata.Clear(); //将要跳出该方法时，删除运行时data

            return cmds;
        }

        /// <summary>
        /// 执行SQL命令
        /// </summary>
        /// <param name="commands">命令集合</param>
        /// <param name="dbBase">数据库对象</param>
        /// <param name="timeout">执行超时时间</param>
        /// <param name="sqlType">SQL类型</param>
        /// <param name="ignoreRowsAffected">是否不检查受影响的行</param>
        private static JObject ExecuteSqlCommands(List<DBCommand> commands, DBBase dbBase, int timeout, string sqlType, bool ignoreRowsAffected)
        {
            if (commands == null || commands.Count == 0)
                return null;
            JObject result = null;
            foreach (DBCommand command in commands)
            {
                DBCommand dbCommand = null;
                if (command is DBConditionCommand)
                {
                    var conditionCmd = (DBConditionCommand)command;
                    int cmdresult = TypeConverter.ObjectToInt(dbBase.ExecuteScalar(conditionCmd.Sql, conditionCmd.Parameters, CommandType.Text, timeout));
                    if (cmdresult > 0)
                        dbCommand = conditionCmd.Command;
                }
                else
                {
                    dbCommand = command;
                }

                if (dbCommand == null)
                    continue;

                if (dbCommand is DBAddCommand)
                {
                    var addCmd = (DBAddCommand)dbCommand;
                    
                    int identityValue = 0;
                    if (!string.IsNullOrWhiteSpace(addCmd.Identity))
                    {
                        identityValue = TypeConverter.ObjectToInt(dbBase.ExecuteScalar(addCmd.Sql, addCmd.Parameters, CommandType.Text, timeout));
                        if (identityValue <= 0 && !ignoreRowsAffected)
                            throw ExceptionHelper.GenException(ErrorCode.E000000005, sqlType);
                        result = new JObject() { { "identity", identityValue } };
                    }
                    else
                    {
                        int dbreturn = dbBase.ExecuteNonQuery(addCmd.Sql, addCmd.Parameters, CommandType.Text, timeout);
                        if (dbreturn <= 0 && !ignoreRowsAffected)
                            throw ExceptionHelper.GenException(ErrorCode.E000000005, sqlType);
                    }

                    
                    if (addCmd.SubCommands != null && addCmd.SubCommands.Count > 0)
                    {
                        foreach (var subCommand in addCmd.SubCommands)
                        {
                            if (subCommand.Parameters == null || subCommand.Parameters.Count == 0)
                                continue;
                            var identityParameters = !string.IsNullOrWhiteSpace(addCmd.Identity) ? subCommand.Parameters.FindAll(p => p.Value?.ToString() == addCmd.Identity) : null;
                            if (identityParameters != null)
                                foreach (var identityParameter in identityParameters)
                                    identityParameter.Value = identityValue;
                        }
                        ExecuteSqlCommands(addCmd.SubCommands, dbBase, timeout, sqlType, ignoreRowsAffected);
                    }
                }
                else if (dbCommand is DBActionCommand)
                {
                    var actionCmd = (DBActionCommand)dbCommand;
                    if (sqlType?.ToLower() == "delete")
                    {
                        ExecuteSqlCommands(actionCmd.SubCommands, dbBase, timeout, sqlType, ignoreRowsAffected);
                        int dbreturn = dbBase.ExecuteNonQuery(actionCmd.Sql, actionCmd.Parameters, CommandType.Text, timeout);
                        if (dbreturn <= 0 && !ignoreRowsAffected)
                            throw ExceptionHelper.GenException(ErrorCode.E000000005, sqlType);
                    }
                    else
                    {
                        int dbreturn = dbBase.ExecuteNonQuery(actionCmd.Sql, actionCmd.Parameters, CommandType.Text, timeout);
                        if (dbreturn <= 0 && !ignoreRowsAffected)
                            throw ExceptionHelper.GenException(ErrorCode.E000000005, sqlType);
                        ExecuteSqlCommands(actionCmd.SubCommands, dbBase, timeout, sqlType, ignoreRowsAffected);
                    }
                }
                else
                {
                    int dbreturn = dbBase.ExecuteNonQuery(dbCommand.Sql, dbCommand.Parameters, CommandType.Text, timeout);
                    if (dbreturn <= 0 && !ignoreRowsAffected)
                        throw ExceptionHelper.GenException(ErrorCode.E000000005, sqlType);
                }
            }
            return result;
        }

        /// <summary>
        /// 解析存储过程参数
        /// </summary>
        /// <param name="config">配置信息</param>
        /// <param name="dataset">数据集合</param>
        /// <param name="hasOutput">是否有输出参数</param>
        /// <returns>参数集合</returns>
        private static List<DBParameter> GetStoredProcedureParameter(JToken config, JObject dataset, out bool hasOutput)
        {
            hasOutput = false;
            var parameters = new List<DBParameter>();
            if (config != null && config.Type == JTokenType.Array)
            {
                foreach (var item in (config as JArray))
                {
                    if (item.Type != JTokenType.Object)
                        continue;
                    var param = item as JObject;
                    var name = param.GetJsonObjectStringValue("name");
                    var type = param.GetJsonObjectStringValue("type");
                    var value = param.GetJsonObjectValue("value");
                    var size = param.GetJsonObjectIntValue("size");
                    var direction = param.GetJsonObjectStringValue("direction")?.ToLower();
                    if (string.IsNullOrWhiteSpace(name))
                        continue;

                    if (value != null && value.Type == JTokenType.String && value.ToString().StartsWith("@"))
                    {
                        value = Utils.GetParameter(value.ToString().TrimStart('@'), dataset).Evaluate();
                    }

                    var parameter = new DBParameter(name, value)
                    {
                        Size = size
                    };
                    if (!string.IsNullOrWhiteSpace(type))
                    {
                        switch (type.ToLower())
                        {
                            case "short":
                                parameter.DataType = DbType.Int16;
                                break;
                            case "int":
                                parameter.DataType = DbType.Int32;
                                break;
                            case "long":
                                parameter.DataType = DbType.Int64;
                                break;
                            case "string":
                                parameter.DataType = DbType.String;
                                break;
                            case "bool":
                                parameter.DataType = DbType.Boolean;
                                break;
                            case "date":
                                parameter.DataType = DbType.Date;
                                break;
                            case "datetime":
                                parameter.DataType = DbType.DateTime;
                                break;
                            case "decimal":
                                parameter.DataType = DbType.Decimal;
                                break;
                            default:
                                DbType dataType = DbType.String;
                                if (!Enum.TryParse(type, true, out dataType))
                                    dataType = DbType.String;
                                break;
                        }
                    }
                    
                    if (direction == "output")
                    {
                        parameter.Direction = ParameterDirection.Output;
                        hasOutput = true;
                    }
                    else if (direction == "inputoutput")
                    {
                        parameter.Direction = ParameterDirection.InputOutput;
                        hasOutput = true;
                    }
                    else if (direction == "returnvalue")
                    {
                        parameter.Direction = ParameterDirection.ReturnValue;
                        hasOutput = true;
                    }
                    parameters.Add(parameter);
                }
            }
            return parameters;
        }

        /// <summary>
        /// 拼接分页sql
        /// </summary>
        /// <param name="dbBase">数据库实体</param>
        /// <param name="sql">sql</param>
        /// <param name="pagination">分页配置</param>
        /// <param name="dataset">参数结合</param>
        /// <returns></returns>
        private static string BuildPagingSql(DBBase dbBase, string sql, JObject pagination, JObject dataset)
        {
            if (pagination == null)
                return sql;

            if (string.IsNullOrWhiteSpace(sql))
                return string.Empty;

            if (!pagination.ContainsProperty("pageindex"))
                pagination.Add("pageindex", "@request.pageindex");
            if (!pagination.ContainsProperty("pagesize"))
                pagination.Add("pagesize", "@request.pagesize");
            if (!pagination.ContainsProperty("pages"))
                pagination.Add("pages", "@request.pages");

            pagination = Utils.ParseObject(pagination, dataset) as JObject;

            int pageindex = pagination.GetJsonObjectIntValue("pageindex", 1);
            int pagesize = pagination.GetJsonObjectIntValue("pagesize", 10);
            int pages = pagination.GetJsonObjectIntValue("pages", 1); //一次取多少页数据，默认1页
            bool isCount = pagination.GetJsonObjectBooleanValue("count");

            string sort = null;
            string defaultsort = pagination.GetJsonObjectStringValue("sort");//默认排序字段，格式：xxx asc或xxx desc
            var dbcommand = ParseSqlString(dbBase, "@request.sort", dataset);
            if (dbcommand != null)
                sort = dbcommand.Sql;

            string orderBy = !string.IsNullOrWhiteSpace(sort) ? sort : defaultsort;

            if (string.IsNullOrWhiteSpace(orderBy))
                return string.Empty;

            if (pageindex <= 0)
                pageindex = 1;

            if (pagesize <= 0)
                pagesize = 10;

            if (pages < 1)
                pages = 1;

            int start = (pageindex - 1) * pagesize;
            int end = (pageindex + pages - 1) * pagesize - 1;

            return dbBase.BuildPagingSql(sql, orderBy, start, end, isCount);
        }

        /// <summary>
        /// 外部API请求
        /// </summary>
        /// <param name="url">API地址</param>
        /// <param name="method">请求类型（GET/POST，默认GET）</param>
        /// <param name="timeout">超时时间（单位秒）</param>
        /// <param name="header">Header信息</param>
        /// <param name="request">请求参数</param>
        /// <param name="config">配置参数</param>
        /// <param name="dataset">数据集合</param>
        /// <returns>请求结果</returns>
        private static JToken HttpApiRequest(string url, string method, int timeout, JToken header, JToken request, JToken responseConfig, JObject config, JObject dataset)
        {
            bool logRequest = config.GetJsonObjectBooleanValue("logRequest", false);
            bool logResponse = config.GetJsonObjectBooleanValue("logResponse", false);
            bool logError = config.GetJsonObjectBooleanValue("logError", true);
            bool ignoreError = config.GetJsonObjectBooleanValue("ignoreError", false);
            bool ignoreSSLCheck = config.GetJsonObjectBooleanValue("ignoreSSLCheck", true);
            bool keepEmptyParam = config.GetJsonObjectBooleanValue("keepEmptyParam", true);
            bool originalResponse = config.GetJsonObjectBooleanValue("originalResponse", false);
            string loggerName = config.GetJsonObjectStringValue("loggerName");
            string globalErrorCode = config.GetJsonObjectStringValue("globalErrorCode");
            string responseCodePath = config.GetJsonObjectStringValue("responseCodePath");
            string responseStatusPath = config.GetJsonObjectStringValue("responseStatusPath");
            string responseMessagePath = config.GetJsonObjectStringValue("responseMessagePath");
            var successCodeValue = config.GetJsonObjectValue("successCodeValue");
            var proxy = config.GetJsonObjectValue("proxy");
            var retry = config.GetJsonObjectIntValue("retry");
            var emailAlert = config.GetJsonObjectValue("emailAlert");

            if (!string.IsNullOrWhiteSpace(url))
            {
                var urls = url.Split('?');
                url = ParseUrlString(urls[0], dataset);
                if (urls.Length > 1)
                    url = string.Format("{0}?{1}", url, ParseUrlString(urls[1], dataset, true, keepEmptyParam));
            }

            string contentType = string.Empty;
            Dictionary<string, string> headerDic = null;
            var headerOjbect = header as JObject;
            if (headerOjbect != null)
            {
                headerDic = new Dictionary<string, string>();
                foreach (var item in headerOjbect)
                {
                    string value = headerOjbect.GetJsonObjectStringValue(item.Key);
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        if (item.Key.ToLower() == "content-type")
                            contentType = value.ToLower();
                        headerDic.Add(item.Key, value);
                    }
                    else if (keepEmptyParam)
                    {
                        headerDic.Add(item.Key, string.Empty);
                    }
                }
            }

            bool xmlContent = contentType.StartsWith("text/xml") || contentType.StartsWith("application/xml");
            string requestString = null;
            if (request != null)
            {
                if (request is JValue)
                {
                    requestString = request.ToString();
                }
                else if (contentType.StartsWith("application/x-www-form-urlencoded"))
                {
                    List<string> reqs = new List<string>();
                    if (request.Type == JTokenType.Object)
                    {
                        var requestObject = request as JObject;
                        foreach (var item in requestObject)
                        {
                            string value = requestObject.GetJsonObjectStringValue(item.Key);
                            if (!string.IsNullOrWhiteSpace(value))
                            {
                                reqs.Add(string.Format("{0}={1}", item.Key, value));
                            }
                            else if (keepEmptyParam)
                            {
                                reqs.Add(string.Format("{0}=", item.Key));
                            }
                        }
                        requestString = string.Join("&", reqs);
                    }
                }
                else
                {
                    requestString = Utils.ParseObjectValues(request, dataset, keepEmptyParam).ToString(Formatting.None);
                    if (xmlContent)
                    {
                        try
                        {
                            string charSet = null;
                            var mediaTypes = contentType.Split(';');
                            if (mediaTypes.Length > 1)
                            {
                                string charSetStr = mediaTypes[1].Trim(' ');
                                if (charSetStr.StartsWith("charset="))
                                    charSet = charSetStr.Substring(8);
                            }
                            var doc = JsonConvert.DeserializeXmlNode(requestString);
                            var xmlDeclaration = doc.CreateXmlDeclaration("1.0", charSet ?? "utf-8", null);
                            doc.PrependChild(xmlDeclaration);
                            requestString = doc.OuterXml;
                        }
                        catch (Exception ex)
                        {
                            throw ExceptionHelper.GenException(ErrorCode.E000000016, ex.Message);
                        }
                    }
                }
            }

            JToken response = null;
            string responseString = null;

            try
            {
                if (logRequest)
                    LogHelper.Info(string.Format("API Request URL: {0}; Header: {1}; Request: {2}", url, JSONHelper.SerializeObject(headerDic), requestString), loggerName);
                DateTime time = DateTime.Now;
                responseString = HttpHelper.HttpApiRequest(url, requestString, method, timeout * 1000, headerDic, proxy?.ToString(), ignoreSSLCheck);
                if (logResponse)
                    LogHelper.Info(string.Format("API Request URL: {0}; Response: {1}; Duration: {2}ms", url, responseString, (DateTime.Now - time).TotalMilliseconds), loggerName);
                if (originalResponse)
                    response = responseString;
                else
                {
                    bool ParseXML = xmlContent;
                    if (!xmlContent)
                    {
                        try
                        {
                            response = JObject.Parse(responseString);
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Warn(string.Format("API Request URL: {0}; Failed to parse response to json", url), ex, loggerName);
                            ParseXML = true;
                        }
                    }

                    if (ParseXML)
                    {
                        try
                        {
                            System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                            doc.LoadXml(responseString);
                            string json = JsonConvert.SerializeXmlNode(doc, Formatting.None);
                            response = JObject.Parse(json);
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Warn(string.Format("API Request URL: {0}; Failed to parse response to xml", url), ex, loggerName);
                            response = responseString;
                        }
                    }
                }

                if (successCodeValue is JValue)
                {
                    if (response == null || (response.Type != JTokenType.Object && response is JValue == false))
                        throw ExceptionHelper.GenException(ErrorCode.E000000011, responseString);
                    if (response is JValue)
                    {
                        if (((JValue)response).Value == null || !((JValue)response).Value.Equals(((JValue)successCodeValue).Value))
                            throw ExceptionHelper.GenException(ErrorCode.E000000011, responseString);
                    }
                    else if (response.Type == JTokenType.Object)
                    {
                        var responseStatus =  Utils.GetValueByPath(string.IsNullOrWhiteSpace(responseStatusPath) ? responseCodePath : responseStatusPath, response);
                        if (responseStatus == null || responseStatus is JValue == false)
                            throw ExceptionHelper.GenException(ErrorCode.E000000012, responseString);
                        if (((JValue)responseStatus).Value == null || !((JValue)responseStatus).Value.Equals(((JValue)successCodeValue).Value))
                        {
                            string msg = null;
                            var responseMessage = Utils.GetValueByPath(responseMessagePath, response);
                            if (responseMessage != null && responseMessage.Type == JTokenType.String)
                            {
                                msg = responseMessage.ToString();
                            }

                            string code = (string.IsNullOrWhiteSpace(responseStatusPath) ? responseStatus : Utils.GetValueByPath(responseCodePath, response))?.ToString();
                            if (APIContext.ErrorMessages.ContainsProperty(code))
                            {
                                if (string.IsNullOrWhiteSpace(msg))
                                    throw ExceptionHelper.GenException("@" + code);
                                throw ExceptionHelper.GenException("@" + code, msg);
                            }
                            else if (!string.IsNullOrWhiteSpace(globalErrorCode))
                            {
                                if (string.IsNullOrWhiteSpace(msg))
                                    throw ExceptionHelper.GenException("@" + globalErrorCode);
                                throw ExceptionHelper.GenException("@" + globalErrorCode, msg);
                            }
                            else if (!string.IsNullOrWhiteSpace(code))
                            {
                                throw new BaseException(code, msg);
                            }
                            throw ExceptionHelper.GenException(ErrorCode.E000000012, responseString);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (retry > 0 && !(e is BaseException))
                {
                    JObject config2 = config.DeepClone() as JObject;
                    config2.SetJsonObjectValue("retry", retry - 1);
                    response = HttpApiRequest(url, method, timeout, header, request, responseConfig, config2, dataset);
                }
                else
                {
                    if (emailAlert != null)
                    {
                        SendApiEmailAlert(url, xmlContent ? HttpUtility.HtmlEncode(requestString) : requestString, request, header, emailAlert, dataset, e);
                    }
                    if (ignoreError || logError)
                    {
                        LogHelper.Error(string.Format("API Request URL: {0}; Request error", url), e, loggerName);
                    }
                    if (!ignoreError)
                    {
                        if (e is System.Net.WebException)
                        {
                            var ex = (System.Net.WebException)e;
                            if (ex.Status == System.Net.WebExceptionStatus.ProtocolError)
                            {
                                var StatusCode = ((System.Net.HttpWebResponse)ex.Response).StatusCode.GetHashCode();
                                var StatusDescription = ((System.Net.HttpWebResponse)ex.Response).StatusDescription;
                                throw ExceptionHelper.GenException(ErrorCode.E000000013, StatusCode, StatusDescription);
                            }
                            else if (ex.Status == System.Net.WebExceptionStatus.Timeout)
                            {
                                throw ExceptionHelper.GenException(ErrorCode.E000000014);
                            }
                            else if (ex.Status == System.Net.WebExceptionStatus.ConnectFailure)
                            {
                                throw ExceptionHelper.GenException(ErrorCode.E000000015);
                            }
                        }
                        throw e;
                    }
                }
            }
            if (responseConfig != null)
            {
                var ds = new JObject();
                if (dataset != null)
                {
                    foreach (var d in dataset)
                        ds.Add(d.Key, d.Value);
                }
                ds.SetJsonObjectValue("context", new JObject() {
                    { "url", url },
                    { "header", header },
                    { "request", request },
                    { "requestString", requestString },
                    { "response", response },
                    { "responseString", responseString }
                });
                return Utils.ParseObject(responseConfig, ds);
            }
            return response;
        }

        private static JToken RunReference(JObject settings, JToken header, JToken query, JToken request, JToken uri,
            string method, string rawQuery, string rawRequest, string language, JObject defaultErrorMessages, JObject errorMessages,
            JToken responseConfig, JObject config, JObject dataset)
        {
            var logRequest = config.GetJsonObjectValue("logRequest");
            var logResponse = config.GetJsonObjectValue("logResponse");
            bool logError = config.GetJsonObjectBooleanValue("logError", true);
            bool ignoreError = config.GetJsonObjectBooleanValue("ignoreError", false);
            bool ignoreAuth = config.GetJsonObjectBooleanValue("ignoreAuth", true);
            //bool keepEmptyParam = config.GetJsonObjectBooleanValue("keepEmptyParam", true);
            string loggerName = config.GetJsonObjectStringValue("loggerName");
            string globalErrorCode = config.GetJsonObjectStringValue("globalErrorCode");
            var emailAlert = config.GetJsonObjectValue("emailAlert");

            string apiFullName = APIContext.ApiFullName;

            JToken response = null;
            JObject subDataset = null;
            JObject responseConfigs = settings.GetJsonObject("response");

            try
            {
                if (logRequest?.Type == JTokenType.Boolean && logRequest.Value<bool>() || logRequest == null && APIContext.LogRequest)
                    LogHelper.Info(string.Format("Reference Name: {0}; Header: {1}; Query: {2}; Request: {3}", apiFullName, header?.ToString(Formatting.None), query?.ToString(Formatting.None), request?.ToString(Formatting.None)), loggerName);
                DateTime time = DateTime.Now;
                subDataset = DatasetInit(settings, header, query, request, uri, method, rawQuery, rawRequest);
                APIContext.Language = language;
                subDataset = DoProcess(settings, subDataset, ignoreAuth);
                var data = responseConfigs.GetJsonObjectValue("data");
                response = ParseResponse(data, subDataset);
                if (logResponse?.Type == JTokenType.Boolean && logResponse.Value<bool>() || logResponse == null && APIContext.LogResponse)
                    LogHelper.Info(string.Format("Reference Name: {0}; Response: {1}; Duration: {2}ms", apiFullName, response?.ToString(Formatting.None), (DateTime.Now - time).TotalMilliseconds), loggerName);
            }
            catch (Exception ex)
            {
                string log = null;
                BaseException exception = null;
                if (ex is ConfigException)
                {
                    log = "{0} config exception";
                    exception = ExceptionHelper.GenException(ErrorCode.E000000002, ex.Message);
                }
                else if (ex is BaseException)
                {
                    log = "{0} base exception";
                    exception = (BaseException)ex;
                }
                else if (ex.Message != null && ex.Message.StartsWith("@"))
                {
                    log = "{0} base exception";
                    exception = ExceptionHelper.GenException(ex.Message);
                }
                else
                {
                    log = "{0} unknown exception";
                    exception = ExceptionHelper.GenException(ErrorCode.E000000001);
                }

                if (exception == null)
                    exception = new BaseException();

                if (!string.IsNullOrWhiteSpace(globalErrorCode))
                {
                    exception = ExceptionHelper.GenException("@" + globalErrorCode, ErrorCode.E000000001, defaultErrorMessages, errorMessages, language, exception.Message);
                }

                if (emailAlert != null)
                {
                    SendRefEmailAlert(apiFullName, rawQuery, rawRequest, header, query, request, emailAlert, dataset, ex);
                }
                if (ignoreError || logError)
                {
                    LogHelper.Error(string.Format("Reference Name: {0}", string.Format(log, apiFullName)), ex, loggerName);
                }
                if (!ignoreError)
                {
                    throw exception;
                }

                if (subDataset == null)
                    subDataset = new JObject();

                subDataset["error"] = new JObject() { { "code", exception.Code }, { "message", exception.Message } };

                var error = responseConfigs.GetJsonObjectValue("error");
                response = ParseResponse(error, subDataset);

                if (response == null)
                {
                    response = new JObject() {
                        { "status", "failure" },
                        { "code", exception.Code },
                        { "message", exception.Message }
                    };
                }
            }
            if (responseConfig != null)
            {
                var ds = new JObject();
                if (dataset != null)
                {
                    foreach (var d in dataset)
                        ds.Add(d.Key, d.Value);
                }
                ds.SetJsonObjectValue("context", new JObject() {
                    { "name", apiFullName },
                    { "header", header },
                    { "query", query },
                    { "queryString", rawQuery },
                    { "request", request },
                    { "requestString", rawRequest },
                    { "response", response }
                });
                return Utils.ParseObject(responseConfig, ds);
            }
            return response;
        }

        private static void SendApiEmailAlert(string url, string requestString, JToken request, JToken header, JToken emailAlert, JObject dataset, Exception e)
        {
            string exCode = null;
            string exMessage = null;
            if (e is BaseException)
            {
                var ex = (BaseException)e;
                exCode = ex.Code;
                exMessage = string.Format("{0} - {1}", ex.Code, ex.Message);
            }
            else
            {
                exMessage = string.Format("{0} - {1}", e.Message, e.StackTrace);
            }
            string subject = "API Request error";
            string message = string.Format("API Request URL: {0}<br/><br/>Request: {1}<br/><br/>Error: {2}", url, requestString, exMessage);

            var context = new JObject() {
                { "url", url },
                { "header", header },
                { "request", request },
                { "requestString", requestString },
                { "code", exCode },
                { "message", exMessage }
            };
            SendEmailAlert(subject, message, context, emailAlert, dataset, e);
        }

        private static void SendRefEmailAlert(string name, string rawQuery, string rawRequest, JToken header, JToken query, JToken request, JToken emailAlert, JObject dataset, Exception e)
        {
            string exCode = null;
            string exMessage = null;
            if (e is BaseException)
            {
                var ex = (BaseException)e;
                exCode = ex.Code;
                exMessage = string.Format("{0} - {1}", ex.Code, ex.Message);
            }
            else
            {
                exMessage = string.Format("{0} - {1}", e.Message, e.StackTrace);
            }
            string subject = "Reference execute error";
            string message = string.Format("Reference Name: {0}<br/><br/>Request: {1}<br/><br/>Error: {2}", name, request?.ToString(Formatting.None), exMessage);

            var context = new JObject() {
                { "name", name },
                { "header", header },
                { "query", query },
                { "queryString", rawQuery },
                { "request", request },
                { "requestString", rawRequest },
                { "code", exCode },
                { "message", exMessage }
            };
            SendEmailAlert(subject, message, context, emailAlert, dataset, e);
        }

        private static void SendEmailAlert(string emailSubject, string emailBody, JToken context, JToken emailAlert, JObject dataset, Exception e)
        {
            if (emailAlert == null || (emailAlert.Type != JTokenType.Boolean && emailAlert.Type != JTokenType.Object && emailAlert.Type != JTokenType.Array))
                return;
            
            var emailRequest = new JObject() {
                { "subject", emailSubject },
                { "message", emailBody }
            };
            if (emailAlert.Type == JTokenType.Boolean)
            {
                if (emailAlert.Value<bool>())
                    new SendEmail().Invoke(dataset, emailRequest);
                return;
            }

            JArray emailAlerts = emailAlert.Type == JTokenType.Array ? (JArray)emailAlert : new JArray() { emailAlert };
            foreach (var item in emailAlerts)
            {
                var emailAlertInfo = item as JObject;
                if (emailAlertInfo == null)
                    continue;
                var condition = emailAlertInfo.ContainsProperty("if") ? ParseCondition(emailAlertInfo.GetJsonObjectValue("if"), dataset) : true;
                if (condition.Type == JTokenType.Boolean && !condition.Value<bool>())
                    continue;
                JObject emailAlertConfig = dataset.GetJsonObject("constant").GetJsonObject("emailAlert");
                JObject emailConfig = emailAlertConfig != null ? emailAlertConfig.DeepClone() as JObject : null;
                if (emailConfig != null)
                    emailConfig.Merge(emailAlertInfo, new JsonMergeSettings() { PropertyNameComparison = StringComparison.OrdinalIgnoreCase });
                else
                    emailConfig = emailAlertInfo;

                if (emailConfig.ContainsProperty("subject") || emailConfig.ContainsProperty("message"))
                {
                    var ds = new JObject();
                    if (dataset != null)
                    {
                        foreach (var d in dataset)
                            ds.Add(d.Key, d.Value);
                    }
                    ds.SetJsonObjectValue("context", context);
                    string subject = emailConfig.GetJsonObjectStringValue("subject");
                    string message = emailConfig.GetJsonObjectStringValue("message");
                    if (!string.IsNullOrWhiteSpace(subject))
                        emailRequest["subject"] = ParseUrlString(subject, ds);
                    if (!string.IsNullOrWhiteSpace(message))
                        emailRequest["message"] = ParseUrlString(message, ds);
                }
                new SendEmail().Invoke(new JObject() { { "constant", new JObject() { { "emailAlert", emailConfig } } } }, emailRequest);
            }
        }
        #endregion
    }
}